<commit_before>// Copyright 2016 NDP Systèmes. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package models

import (
	"github.com/lib/pq"
	"github.com/npiganeau/yep/yep/models/types"
	"github.com/npiganeau/yep/yep/tools/logging"
)

const DBSerializationMaxRetries uint8 = 5

// An Environment stores various contextual data used by the models:
// - the database cursor (current open transaction),
// - the current user ID (for access rights checking)
// - the current context (for storing arbitrary metadata).
// The Environment also stores caches.
type Environment struct {
	cr      *Cursor
	uid     int64
	context *types.Context
	cache   *cache
	retries uint8
}

// Cr returns a pointer to the Cursor of the Environment
func (env Environment) Cr() *Cursor {
	return env.cr
}

// Uid returns the user id of the Environment
func (env Environment) Uid() int64 {
	return env.uid
}

// Context returns the Context of the Environment
func (env Environment) Context() *types.Context {
	return env.context
}

// commit the transaction of this environment.
//
// WARNING: Do NOT call Commit on Environment instances that you
// did not create yourself with NewEnvironment. The framework will
// automatically commit the Environment.
func (env Environment) commit() {
	env.cr.tx.Commit()
}

// rollback the transaction of this environment.
//
// WARNING: Do NOT call Rollback on Environment instances that you
// did not create yourself with NewEnvironment. Just panic instead
// for the framework to roll back automatically for you.
func (env Environment) rollback() {
	env.cr.tx.Rollback()
}

// newEnvironment returns a new Environment with the given parameters
// in a new DB transaction.
//
// WARNING: Callers to NewEnvironment should ensure to either call Commit()
// or Rollback() on the returned Environment after operation to release
// the database connection.
func newEnvironment(uid int64, context ...types.Context) Environment {
	var ctx types.Context
	if len(context) > 0 {
		ctx = context[0]
	}
	env := Environment{
		cr:      newCursor(db),
		uid:     uid,
		context: &ctx,
		cache:   newCache(),
	}
	return env
}

// ExecuteInNewEnvironment executes the given fnct in a new Environment
// within a new transaction.
//
// This function commits the transaction if everything went right or
// rolls it back otherwise, returning an arror. Database serialization
// errors are automatically retried several times before returning an
// error if they still occur.
func ExecuteInNewEnvironment(uid int64, fnct func(Environment)) (rError error) {
	env := newEnvironment(uid)
	defer func() {
		if r := recover(); r != nil {
			env.rollback()
			if err, ok := r.(pq.Error); ok && err.Code.Class() == "40" {
				// Transaction error
				env.retries++
				if env.retries < DBSerializationMaxRetries {
					if ExecuteInNewEnvironment(uid, fnct) == nil {
						rError = nil
						return
					}
				}
			}
			rError = logging.LogPanicData(r)
			return
		}
		env.commit()
	}()
	fnct(env)
	return
}

// SimulateInNewEnvironment executes the given fnct in a new Environment
// within a new transaction and rolls back the transaction at the end.
//
// This function always rolls back the transaction but returns an error
// only if fnct panicked during its execution.
func SimulateInNewEnvironment(uid int64, fnct func(Environment)) (rError error) {
	env := newEnvironment(uid)
	defer func() {
		env.rollback()
		if r := recover(); r != nil {
			rError = logging.LogPanicData(r)
			return
		}
	}()
	fnct(env)
	return
}

// Pool returns an empty RecordCollection for the given modelName
func (env Environment) Pool(modelName string) RecordCollection {
	return newRecordCollection(env, modelName)
}
<commit_msg>[REF] Lint correction<commit_after>// Copyright 2016 NDP Systèmes. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package models

import (
	"github.com/lib/pq"
	"github.com/npiganeau/yep/yep/models/types"
	"github.com/npiganeau/yep/yep/tools/logging"
)

// DBSerializationMaxRetries defines the number of time a
// transaction that failed due to serialization error should
// be retried.
const DBSerializationMaxRetries uint8 = 5

// An Environment stores various contextual data used by the models:
// - the database cursor (current open transaction),
// - the current user ID (for access rights checking)
// - the current context (for storing arbitrary metadata).
// The Environment also stores caches.
type Environment struct {
	cr      *Cursor
	uid     int64
	context *types.Context
	cache   *cache
	retries uint8
}

// Cr returns a pointer to the Cursor of the Environment
func (env Environment) Cr() *Cursor {
	return env.cr
}

// Uid returns the user id of the Environment
func (env Environment) Uid() int64 {
	return env.uid
}

// Context returns the Context of the Environment
func (env Environment) Context() *types.Context {
	return env.context
}

// commit the transaction of this environment.
//
// WARNING: Do NOT call Commit on Environment instances that you
// did not create yourself with NewEnvironment. The framework will
// automatically commit the Environment.
func (env Environment) commit() {
	env.cr.tx.Commit()
}

// rollback the transaction of this environment.
//
// WARNING: Do NOT call Rollback on Environment instances that you
// did not create yourself with NewEnvironment. Just panic instead
// for the framework to roll back automatically for you.
func (env Environment) rollback() {
	env.cr.tx.Rollback()
}

// newEnvironment returns a new Environment with the given parameters
// in a new DB transaction.
//
// WARNING: Callers to NewEnvironment should ensure to either call Commit()
// or Rollback() on the returned Environment after operation to release
// the database connection.
func newEnvironment(uid int64, context ...types.Context) Environment {
	var ctx types.Context
	if len(context) > 0 {
		ctx = context[0]
	}
	env := Environment{
		cr:      newCursor(db),
		uid:     uid,
		context: &ctx,
		cache:   newCache(),
	}
	return env
}

// ExecuteInNewEnvironment executes the given fnct in a new Environment
// within a new transaction.
//
// This function commits the transaction if everything went right or
// rolls it back otherwise, returning an arror. Database serialization
// errors are automatically retried several times before returning an
// error if they still occur.
func ExecuteInNewEnvironment(uid int64, fnct func(Environment)) (rError error) {
	env := newEnvironment(uid)
	defer func() {
		if r := recover(); r != nil {
			env.rollback()
			if err, ok := r.(pq.Error); ok && err.Code.Class() == "40" {
				// Transaction error
				env.retries++
				if env.retries < DBSerializationMaxRetries {
					if ExecuteInNewEnvironment(uid, fnct) == nil {
						rError = nil
						return
					}
				}
			}
			rError = logging.LogPanicData(r)
			return
		}
		env.commit()
	}()
	fnct(env)
	return
}

// SimulateInNewEnvironment executes the given fnct in a new Environment
// within a new transaction and rolls back the transaction at the end.
//
// This function always rolls back the transaction but returns an error
// only if fnct panicked during its execution.
func SimulateInNewEnvironment(uid int64, fnct func(Environment)) (rError error) {
	env := newEnvironment(uid)
	defer func() {
		env.rollback()
		if r := recover(); r != nil {
			rError = logging.LogPanicData(r)
			return
		}
	}()
	fnct(env)
	return
}

// Pool returns an empty RecordCollection for the given modelName
func (env Environment) Pool(modelName string) RecordCollection {
	return newRecordCollection(env, modelName)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"gopkg.in/yaml.v2"

	"github.com/themalkolm/venom"
)

var RootCmd = &cobra.Command{
	Use:          "example",
	Short:        "Do example things.",
	SilenceUsage: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		//
		// 12-factor automatically registered all flags in viper configuration. This
		// means viper is already configured with all values provided via cli, env
		// and defaults if any: flag > env > default.
		//
		// The following trick with having a struct representing our configuration
		// allows us to escape dynamic nature of viper and have a static Config
		// structure.
		//
		var cfg Config
		err := viper.Unmarshal(&cfg)
		if err != nil {
			return err
		}
		return runE(&cfg)
	},
}

type Config struct {
	Foo    string `mapstructure:"foo"`
	FooBar string `mapstructure:"foo-bar"`
}

func init() {
	//
	// You need to define flags as usual.
	//
	RootCmd.PersistentFlags().String("foo", "", "Some foonees must be set")
	RootCmd.PersistentFlags().String("foo-bar", "", "Some barness must be set")

	//
	// Enable 12-factor application so magic happens
	//
	err := venom.TwelveFactor("example", RootCmd.PersistentFlags())
	if err != nil {
		log.Fatal(err)
	}
}

func runE(cfg *Config) error {
	//
	// Here we simply dump passed config object. This has nothing to do
	// with 12-factor, just make it easy to play with.
	//
	b, err := yaml.Marshal(&cfg)
	if err != nil {
		return err
	}
	fmt.Printf("%+v", string(b))
	return nil
}
<commit_msg>Use TwelveFactorCmd in _example<commit_after>package main

import (
	"fmt"
	"log"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"gopkg.in/yaml.v2"

	"github.com/themalkolm/venom"
)

var RootCmd = &cobra.Command{
	Use:          "example",
	Short:        "Do example things.",
	SilenceUsage: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		//
		// 12-factor automatically registered all flags in viper configuration. This
		// means viper is already configured with all values provided via cli, env
		// and defaults if any: flag > env > default.
		//
		// The following trick with having a struct representing our configuration
		// allows us to escape dynamic nature of viper and have a static Config
		// structure.
		//
		var cfg Config
		err := viper.Unmarshal(&cfg)
		if err != nil {
			return err
		}
		return runE(&cfg)
	},
}

type Config struct {
	Foo    string `mapstructure:"foo"`
	FooBar string `mapstructure:"foo-bar"`
}

func init() {
	//
	// You need to define flags as usual.
	//
	RootCmd.PersistentFlags().String("foo", "", "Some foonees must be set")
	RootCmd.PersistentFlags().String("foo-bar", "", "Some barness must be set")

	//
	// Enable 12-factor application so magic happens
	//
	err := venom.TwelveFactorCmd(RootCmd, RootCmd.PersistentFlags())
	if err != nil {
		log.Fatal(err)
	}
}

func runE(cfg *Config) error {
	//
	// Here we simply dump passed config object. This has nothing to do
	// with 12-factor, just make it easy to play with.
	//
	b, err := yaml.Marshal(&cfg)
	if err != nil {
		return err
	}
	fmt.Printf("%+v", string(b))
	return nil
}
<|endoftext|>


<commit_before>//  Copyright (c) 2012 Couchbase, Inc.
//  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
//  except in compliance with the License. You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//  Unless required by applicable law or agreed to in writing, software distributed under the
//  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
//  either express or implied. See the License for the specific language governing permissions
//  and limitations under the License.

package rest

import (
	"encoding/json"
	"fmt"
	"net/http"
	"testing"

	"github.com/couchbase/sg-bucket"
	"github.com/couchbase/sync_gateway/channels"

	"github.com/couchbaselabs/go.assert"
)

func TestDesignDocs(t *testing.T) {
	var rt restTester
	response := rt.sendRequest("GET", "/db/_design/foo", "")
	assertStatus(t, response, 403)
	response = rt.sendRequest("PUT", "/db/_design/foo", `{"prop":true}`)
	assertStatus(t, response, 403)
	response = rt.sendRequest("DELETE", "/db/_design/foo", "")
	assertStatus(t, response, 403)

	response = rt.sendAdminRequest("GET", "/db/_design/foo", "")
	assertStatus(t, response, 404)
	response = rt.sendAdminRequest("PUT", "/db/_design/foo", `{"prop":true}`)
	assertStatus(t, response, 201)
	response = rt.sendAdminRequest("GET", "/db/_design/foo", "")
	assertStatus(t, response, 200)
	response = rt.sendAdminRequest("GET", "/db/_design%2ffoo", "")
	assertStatus(t, response, 200)
	response = rt.sendAdminRequest("GET", "/db/_design%2Ffoo", "")
	assertStatus(t, response, 200)
	response = rt.sendAdminRequest("DELETE", "/db/_design/foo", "")
	assertStatus(t, response, 200)
	response = rt.sendAdminRequest("PUT", "/db/_design/sync_gateway", "{}")
	assertStatus(t, response, 403)
	response = rt.sendAdminRequest("GET", "/db/_design/sync_gateway", "")
	assertStatus(t, response, 200)
}

func TestViewQuery(t *testing.T) {
	var rt restTester
	response := rt.sendAdminRequest("PUT", "/db/_design/foo", `{"views":{"bar": {"map": "function(doc) {emit(doc.key, doc.value);}"}}}`)
	assertStatus(t, response, 201)
	response = rt.sendRequest("PUT", "/db/doc1", `{"key":10, "value":"ten"}`)
	assertStatus(t, response, 201)
	response = rt.sendRequest("PUT", "/db/doc2", `{"key":7, "value":"seven"}`)
	assertStatus(t, response, 201)

	response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/bar", ``)
	assertStatus(t, response, 200)
	var result sgbucket.ViewResult
	json.Unmarshal(response.Body.Bytes(), &result)
	assert.Equals(t, len(result.Rows), 2)
	assert.DeepEquals(t, result.Rows[0], &sgbucket.ViewRow{ID: "doc2", Key: 7.0, Value: "seven"})
	assert.DeepEquals(t, result.Rows[1], &sgbucket.ViewRow{ID: "doc1", Key: 10.0, Value: "ten"})

	response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/bar?endkey=9", ``)
	assertStatus(t, response, 200)
	json.Unmarshal(response.Body.Bytes(), &result)
	assert.Equals(t, len(result.Rows), 1)
	assert.DeepEquals(t, result.Rows[0], &sgbucket.ViewRow{ID: "doc2", Key: 7.0, Value: "seven"})

	response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/bar?endkey=9&include_docs=true", ``)
	assertStatus(t, response, 200)
	json.Unmarshal(response.Body.Bytes(), &result)
	assert.Equals(t, len(result.Rows), 1)
	assert.DeepEquals(t, *result.Rows[0].Doc, map[string]interface{}{"key": 7.0, "value": "seven"})
}

func TestUserViewQuery(t *testing.T) {
	rt := restTester{syncFn: `function(doc) {channel(doc.channel)}`}
	a := rt.ServerContext().Database("db").Authenticator()
	// Create a view:
	response := rt.sendAdminRequest("PUT", "/db/_design/foo", `{"views":{"bar": {"map": "function(doc) {emit(doc.key, doc.value);}"}}}`)
	assertStatus(t, response, 201)
	// Create docs:
	response = rt.sendRequest("PUT", "/db/doc1", `{"key":10, "value":"ten", "channel":"W"}`)
	assertStatus(t, response, 201)
	response = rt.sendRequest("PUT", "/db/doc2", `{"key":7, "value":"seven", "channel":"Q"}`)
	assertStatus(t, response, 201)
	// Create a user:
	quinn, _ := a.NewUser("quinn", "123456", channels.SetOf("Q", "q"))
	a.Save(quinn)

	// Have the user query the view:
	request, _ := http.NewRequest("GET", "/db/_design/foo/_view/bar?include_docs=true", nil)
	request.SetBasicAuth("quinn", "123456")
	response = rt.send(request)
	assertStatus(t, response, 200)
	var result sgbucket.ViewResult
	json.Unmarshal(response.Body.Bytes(), &result)
	assert.Equals(t, len(result.Rows), 1)
	row := result.Rows[0]
	assert.Equals(t, row.Key, float64(7))
	assert.Equals(t, row.Value, "seven")
	assert.DeepEquals(t, *row.Doc, map[string]interface{}{"key": 7.0, "value": "seven", "channel": "Q"})

	// Admin should see both rows:
	response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/bar", ``)
	assertStatus(t, response, 200)
	json.Unmarshal(response.Body.Bytes(), &result)
	assert.Equals(t, len(result.Rows), 2)
	row = result.Rows[0]
	assert.Equals(t, row.Key, float64(7))
	assert.Equals(t, row.Value, "seven")
	assert.DeepEquals(t, *row.Doc, map[string]interface{}{"key": 7.0, "value": "seven", "channel": "Q"})
	row = result.Rows[1]
	assert.Equals(t, row.Key, float64(10))
	assert.Equals(t, row.Value, "ten")
	assert.DeepEquals(t, *row.Doc, map[string]interface{}{"key": 10.0, "value": "ten", "channel": "W"})

	// Make sure users are not allowed to query internal views:
	request, _ = http.NewRequest("GET", "/db/_design/sync_gateway/_view/access", nil)
	request.SetBasicAuth("quinn", "123456")
	response = rt.send(request)
	assertStatus(t, response, 403)
}

// This includes a fix for #857
func TestAdminReduceViewQuery(t *testing.T) {

	rt := restTester{syncFn: `function(doc) {channel(doc.channel)}`}

	// Create a view with a reduce:
	response := rt.sendAdminRequest("PUT", "/db/_design/foo", `{"views":{"bar": {"map": "function(doc) {emit(doc.key, doc.value);}", "reduce": "_count"}}}`)
	assertStatus(t, response, 201)

	for i := 0; i < 9; i++ {
		// Create docs:
		response = rt.sendRequest("PUT", fmt.Sprintf("/db/doc%v", i), `{"key":0, "value":"0", "channel":"W"}`)
		assertStatus(t, response, 201)

	}
	response = rt.sendRequest("PUT", fmt.Sprintf("/db/doc%v", 10), `{"key":1, "value":"0", "channel":"W"}`)
	assertStatus(t, response, 201)

	var result sgbucket.ViewResult

	// Admin view query:
	response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/bar?reduce=true", ``)
	assertStatus(t, response, 200)
	json.Unmarshal(response.Body.Bytes(), &result)

	// we should get 1 row with the reduce result
	assert.Equals(t, len(result.Rows), 1)
	row := result.Rows[0]
	value := row.Value.(float64)
	assert.True(t, value == 10)

	// todo support group reduce, see #955
	// // test group=true
	// response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/bar?reduce=true&group=true", ``)
	// assertStatus(t, response, 200)
	// json.Unmarshal(response.Body.Bytes(), &result)
	// // we should get 2 rows with the reduce result
	// assert.Equals(t, len(result.Rows), 2)
	// row = result.Rows[0]
	// value = row.Value.(float64)
	// assert.True(t, value == 9)
	// row = result.Rows[1]
	// value = row.Value.(float64)
	// assert.True(t, value == 1)
}

func TestAdminReduceSumQuery(t *testing.T) {

	rt := restTester{syncFn: `function(doc) {channel(doc.channel)}`}

	// Create a view with a reduce:
	response := rt.sendAdminRequest("PUT", "/db/_design/foo", `{"options":{"raw":true},"views":{"bar": {"map": "function(doc) {if (doc.key && doc.value) emit(doc.key, doc.value);}", "reduce": "_sum"}}}`)
	assertStatus(t, response, 201)

	for i := 0; i < 9; i++ {
		// Create docs:
		response = rt.sendRequest("PUT", fmt.Sprintf("/db/doc%v", i), `{"key":"A", "value":1}`)
		assertStatus(t, response, 201)

	}
	response = rt.sendRequest("PUT", fmt.Sprintf("/db/doc%v", 10), `{"key":"B", "value":99}`)
	assertStatus(t, response, 201)

	var result sgbucket.ViewResult

	// Admin view query:
	response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/bar?reduce=true", ``)
	assertStatus(t, response, 200)
	json.Unmarshal(response.Body.Bytes(), &result)

	// we should get 1 row with the reduce result
	assert.Equals(t, len(result.Rows), 1)
	row := result.Rows[0]
	value := row.Value.(float64)
	assert.Equals(t, value, 108.0)
}
<commit_msg>Added test to make sure multiple views in a design doc are all processed. The test passes in this basic case, but if the entire doc is emitted as the value, then that doc contains the '_sync' property.<commit_after>//  Copyright (c) 2012 Couchbase, Inc.
//  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
//  except in compliance with the License. You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//  Unless required by applicable law or agreed to in writing, software distributed under the
//  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
//  either express or implied. See the License for the specific language governing permissions
//  and limitations under the License.

package rest

import (
	"encoding/json"
	"fmt"
	"net/http"
	"testing"

	"github.com/couchbase/sg-bucket"
	"github.com/couchbase/sync_gateway/channels"

	"github.com/couchbaselabs/go.assert"
)

func TestDesignDocs(t *testing.T) {
	var rt restTester
	response := rt.sendRequest("GET", "/db/_design/foo", "")
	assertStatus(t, response, 403)
	response = rt.sendRequest("PUT", "/db/_design/foo", `{"prop":true}`)
	assertStatus(t, response, 403)
	response = rt.sendRequest("DELETE", "/db/_design/foo", "")
	assertStatus(t, response, 403)

	response = rt.sendAdminRequest("GET", "/db/_design/foo", "")
	assertStatus(t, response, 404)
	response = rt.sendAdminRequest("PUT", "/db/_design/foo", `{"prop":true}`)
	assertStatus(t, response, 201)
	response = rt.sendAdminRequest("GET", "/db/_design/foo", "")
	assertStatus(t, response, 200)
	response = rt.sendAdminRequest("GET", "/db/_design%2ffoo", "")
	assertStatus(t, response, 200)
	response = rt.sendAdminRequest("GET", "/db/_design%2Ffoo", "")
	assertStatus(t, response, 200)
	response = rt.sendAdminRequest("DELETE", "/db/_design/foo", "")
	assertStatus(t, response, 200)
	response = rt.sendAdminRequest("PUT", "/db/_design/sync_gateway", "{}")
	assertStatus(t, response, 403)
	response = rt.sendAdminRequest("GET", "/db/_design/sync_gateway", "")
	assertStatus(t, response, 200)
}

func TestViewQuery(t *testing.T) {
	var rt restTester
	response := rt.sendAdminRequest("PUT", "/db/_design/foo", `{"views":{"bar": {"map": "function(doc) {emit(doc.key, doc.value);}"}}}`)
	assertStatus(t, response, 201)
	response = rt.sendRequest("PUT", "/db/doc1", `{"key":10, "value":"ten"}`)
	assertStatus(t, response, 201)
	response = rt.sendRequest("PUT", "/db/doc2", `{"key":7, "value":"seven"}`)
	assertStatus(t, response, 201)

	response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/bar", ``)
	assertStatus(t, response, 200)
	var result sgbucket.ViewResult
	json.Unmarshal(response.Body.Bytes(), &result)
	assert.Equals(t, len(result.Rows), 2)
	assert.DeepEquals(t, result.Rows[0], &sgbucket.ViewRow{ID: "doc2", Key: 7.0, Value: "seven"})
	assert.DeepEquals(t, result.Rows[1], &sgbucket.ViewRow{ID: "doc1", Key: 10.0, Value: "ten"})

	response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/bar?endkey=9", ``)
	assertStatus(t, response, 200)
	json.Unmarshal(response.Body.Bytes(), &result)
	assert.Equals(t, len(result.Rows), 1)
	assert.DeepEquals(t, result.Rows[0], &sgbucket.ViewRow{ID: "doc2", Key: 7.0, Value: "seven"})

	response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/bar?endkey=9&include_docs=true", ``)
	assertStatus(t, response, 200)
	json.Unmarshal(response.Body.Bytes(), &result)
	assert.Equals(t, len(result.Rows), 1)
	assert.DeepEquals(t, *result.Rows[0].Doc, map[string]interface{}{"key": 7.0, "value": "seven"})
}

//Tests #1109, where design doc contains multiple views
func failingTestViewQueryMultipleViews(t *testing.T) {
	var rt restTester
	//Define three views
	response := rt.sendAdminRequest("PUT", "/db/_design/foo", `{"views": {"by_fname": {"map": "function (doc, meta) { emit(doc.fname, null); }"},"by_lname": {"map": "function (doc, meta) { emit(doc.lname, null); }"},"by_age": {"map": "function (doc, meta) { emit(doc.age, doc); }"}}}`)
	assertStatus(t, response, 201)
	response = rt.sendRequest("PUT", "/db/doc1", `{"fname": "Alice", "lname":"Ten", "age":10}`)
	assertStatus(t, response, 201)
	response = rt.sendRequest("PUT", "/db/doc2", `{"fname": "Bob", "lname":"Seven", "age":7}`)
	assertStatus(t, response, 201)

	response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/by_age", ``)
	assertStatus(t, response, 200)
	var result sgbucket.ViewResult
	json.Unmarshal(response.Body.Bytes(), &result)
	assert.Equals(t, len(result.Rows), 2)
	assert.DeepEquals(t, result.Rows[0], &sgbucket.ViewRow{ID: "doc2", Key: 7.0, Value: interface {}(nil)})
	assert.DeepEquals(t, result.Rows[1], &sgbucket.ViewRow{ID: "doc1", Key: 10.0, Value: interface {}(nil)})

	response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/by_fname", ``)
	assertStatus(t, response, 200)
	json.Unmarshal(response.Body.Bytes(), &result)
	assert.Equals(t, len(result.Rows), 2)
	assert.DeepEquals(t, result.Rows[0], &sgbucket.ViewRow{ID: "doc1", Key: "Alice" , Value: interface {}(nil)})
	assert.DeepEquals(t, result.Rows[1], &sgbucket.ViewRow{ID: "doc2", Key: "Bob" , Value: interface {}(nil)})

	response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/by_lname", ``)
	assertStatus(t, response, 200)
	json.Unmarshal(response.Body.Bytes(), &result)
	assert.Equals(t, len(result.Rows), 2)
	assert.DeepEquals(t, result.Rows[0], &sgbucket.ViewRow{ID: "doc2", Key: "Seven" , Value: interface {}(nil)})
	assert.DeepEquals(t, result.Rows[1], &sgbucket.ViewRow{ID: "doc1", Key: "Ten" , Value: interface {}(nil)})
}

func TestUserViewQuery(t *testing.T) {
	rt := restTester{syncFn: `function(doc) {channel(doc.channel)}`}
	a := rt.ServerContext().Database("db").Authenticator()
	// Create a view:
	response := rt.sendAdminRequest("PUT", "/db/_design/foo", `{"views":{"bar": {"map": "function(doc) {emit(doc.key, doc.value);}"}}}`)
	assertStatus(t, response, 201)
	// Create docs:
	response = rt.sendRequest("PUT", "/db/doc1", `{"key":10, "value":"ten", "channel":"W"}`)
	assertStatus(t, response, 201)
	response = rt.sendRequest("PUT", "/db/doc2", `{"key":7, "value":"seven", "channel":"Q"}`)
	assertStatus(t, response, 201)
	// Create a user:
	quinn, _ := a.NewUser("quinn", "123456", channels.SetOf("Q", "q"))
	a.Save(quinn)

	// Have the user query the view:
	request, _ := http.NewRequest("GET", "/db/_design/foo/_view/bar?include_docs=true", nil)
	request.SetBasicAuth("quinn", "123456")
	response = rt.send(request)
	assertStatus(t, response, 200)
	var result sgbucket.ViewResult
	json.Unmarshal(response.Body.Bytes(), &result)
	assert.Equals(t, len(result.Rows), 1)
	row := result.Rows[0]
	assert.Equals(t, row.Key, float64(7))
	assert.Equals(t, row.Value, "seven")
	assert.DeepEquals(t, *row.Doc, map[string]interface{}{"key": 7.0, "value": "seven", "channel": "Q"})

	// Admin should see both rows:
	response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/bar", ``)
	assertStatus(t, response, 200)
	json.Unmarshal(response.Body.Bytes(), &result)
	assert.Equals(t, len(result.Rows), 2)
	row = result.Rows[0]
	assert.Equals(t, row.Key, float64(7))
	assert.Equals(t, row.Value, "seven")
	assert.DeepEquals(t, *row.Doc, map[string]interface{}{"key": 7.0, "value": "seven", "channel": "Q"})
	row = result.Rows[1]
	assert.Equals(t, row.Key, float64(10))
	assert.Equals(t, row.Value, "ten")
	assert.DeepEquals(t, *row.Doc, map[string]interface{}{"key": 10.0, "value": "ten", "channel": "W"})

	// Make sure users are not allowed to query internal views:
	request, _ = http.NewRequest("GET", "/db/_design/sync_gateway/_view/access", nil)
	request.SetBasicAuth("quinn", "123456")
	response = rt.send(request)
	assertStatus(t, response, 403)
}

// This includes a fix for #857
func TestAdminReduceViewQuery(t *testing.T) {

	rt := restTester{syncFn: `function(doc) {channel(doc.channel)}`}

	// Create a view with a reduce:
	response := rt.sendAdminRequest("PUT", "/db/_design/foo", `{"views":{"bar": {"map": "function(doc) {emit(doc.key, doc.value);}", "reduce": "_count"}}}`)
	assertStatus(t, response, 201)

	for i := 0; i < 9; i++ {
		// Create docs:
		response = rt.sendRequest("PUT", fmt.Sprintf("/db/doc%v", i), `{"key":0, "value":"0", "channel":"W"}`)
		assertStatus(t, response, 201)

	}
	response = rt.sendRequest("PUT", fmt.Sprintf("/db/doc%v", 10), `{"key":1, "value":"0", "channel":"W"}`)
	assertStatus(t, response, 201)

	var result sgbucket.ViewResult

	// Admin view query:
	response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/bar?reduce=true", ``)
	assertStatus(t, response, 200)
	json.Unmarshal(response.Body.Bytes(), &result)

	// we should get 1 row with the reduce result
	assert.Equals(t, len(result.Rows), 1)
	row := result.Rows[0]
	value := row.Value.(float64)
	assert.True(t, value == 10)

	// todo support group reduce, see #955
	// // test group=true
	// response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/bar?reduce=true&group=true", ``)
	// assertStatus(t, response, 200)
	// json.Unmarshal(response.Body.Bytes(), &result)
	// // we should get 2 rows with the reduce result
	// assert.Equals(t, len(result.Rows), 2)
	// row = result.Rows[0]
	// value = row.Value.(float64)
	// assert.True(t, value == 9)
	// row = result.Rows[1]
	// value = row.Value.(float64)
	// assert.True(t, value == 1)
}

func TestAdminReduceSumQuery(t *testing.T) {

	rt := restTester{syncFn: `function(doc) {channel(doc.channel)}`}

	// Create a view with a reduce:
	response := rt.sendAdminRequest("PUT", "/db/_design/foo", `{"options":{"raw":true},"views":{"bar": {"map": "function(doc) {if (doc.key && doc.value) emit(doc.key, doc.value);}", "reduce": "_sum"}}}`)
	assertStatus(t, response, 201)

	for i := 0; i < 9; i++ {
		// Create docs:
		response = rt.sendRequest("PUT", fmt.Sprintf("/db/doc%v", i), `{"key":"A", "value":1}`)
		assertStatus(t, response, 201)

	}
	response = rt.sendRequest("PUT", fmt.Sprintf("/db/doc%v", 10), `{"key":"B", "value":99}`)
	assertStatus(t, response, 201)

	var result sgbucket.ViewResult

	// Admin view query:
	response = rt.sendAdminRequest("GET", "/db/_design/foo/_view/bar?reduce=true", ``)
	assertStatus(t, response, 200)
	json.Unmarshal(response.Body.Bytes(), &result)

	// we should get 1 row with the reduce result
	assert.Equals(t, len(result.Rows), 1)
	row := result.Rows[0]
	value := row.Value.(float64)
	assert.Equals(t, value, 108.0)
}
<|endoftext|>


<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package externals

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"testing"

	"github.com/stretchr/testify/require"

	libkb "github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol/keybase1"
)

func containsString(xs []string, target string) bool {
	for _, x := range xs {
		if x == target {
			return true
		}
	}
	return false
}

func TestParseImplicitTeamTLFName(t *testing.T) {
	badNames := []string{
		"foobar",
		"/keybas/public/foo,bar",
		"/keybase/publi/foo,bar",
		"/keybase/public/foobar,foo:@bar",
		"/keybase/public/foobar,foobar::",
		"/keybase/public/foobar,alice@fakemedia",
		"/keybase/public/foobar__underscore",
	}
	for _, badName := range badNames {
		_, err := libkb.ParseImplicitTeamTLFName(MakeAssertionContext(), badName)
		require.Error(t, err)
	}
	goodName := "/keybase/public/dave,twitter:alice,bob@facebook,carol@keybase,echo"
	name, err := libkb.ParseImplicitTeamTLFName(MakeAssertionContext(), goodName)
	require.NoError(t, err)
	require.Equal(t, name.IsPrivate, false)
	require.Equal(t, len(name.Writers.KeybaseUsers), 3)
	require.Equal(t, len(name.Writers.UnresolvedUsers), 2)
	require.True(t, containsString(name.Writers.KeybaseUsers, "dave"))
	require.True(t, containsString(name.Writers.KeybaseUsers, "carol"))
	require.True(t, containsString(name.Writers.KeybaseUsers, "echo"))

	firstSocial := name.Writers.UnresolvedUsers[0]
	secondSocial := name.Writers.UnresolvedUsers[1]
	aliceExpected := keybase1.SocialAssertion{User: "alice", Service: keybase1.SocialAssertionService("twitter")}
	bobExpected := keybase1.SocialAssertion{User: "bob", Service: keybase1.SocialAssertionService("facebook")}
	require.True(t, firstSocial != secondSocial)
	require.True(t, firstSocial == aliceExpected || firstSocial == bobExpected)
	require.True(t, secondSocial == aliceExpected || secondSocial == bobExpected)
}

func TestPartImplicitTeamTLFNameEvenMore(t *testing.T) {
	tests := []struct {
		input  string
		output *keybase1.ImplicitTeamName
	}{
		{
			"/keybase/private/bob,alice#bob,alice",
			&keybase1.ImplicitTeamName{
				IsPrivate: true,
				Writers: keybase1.ImplicitTeamUserSet{
					KeybaseUsers: []string{"alice", "bob"},
				},
			},
		},
		{
			"/keybase/private/bob,alice#bob,alice,doug,charlie",
			&keybase1.ImplicitTeamName{
				IsPrivate: true,
				Writers: keybase1.ImplicitTeamUserSet{
					KeybaseUsers: []string{"alice", "bob"},
				},
				Readers: keybase1.ImplicitTeamUserSet{
					KeybaseUsers: []string{"charlie", "doug"},
				},
			},
		},
		{
			"/keybase/private/bob,alice,jason@github#bob,alice,doug,charlie,github:jason,keith@twitter,twitter:keith,beth@reddit,keith@github",
			&keybase1.ImplicitTeamName{
				IsPrivate: true,
				Writers: keybase1.ImplicitTeamUserSet{
					KeybaseUsers: []string{"alice", "bob"},
					UnresolvedUsers: []keybase1.SocialAssertion{
						keybase1.SocialAssertion{
							User:    "jason",
							Service: keybase1.SocialAssertionService("github"),
						},
					},
				},
				Readers: keybase1.ImplicitTeamUserSet{
					KeybaseUsers: []string{"charlie", "doug"},
					UnresolvedUsers: []keybase1.SocialAssertion{
						keybase1.SocialAssertion{
							User:    "beth",
							Service: keybase1.SocialAssertionService("reddit"),
						},
						keybase1.SocialAssertion{
							User:    "keith",
							Service: keybase1.SocialAssertionService("github"),
						},
						keybase1.SocialAssertion{
							User:    "keith",
							Service: keybase1.SocialAssertionService("twitter"),
						},
					},
				},
			},
		},
		{
			"/keybase/private/keybase:alice,bob@keybase#bob,alice",
			&keybase1.ImplicitTeamName{
				IsPrivate: true,
				Writers: keybase1.ImplicitTeamUserSet{
					KeybaseUsers: []string{"alice", "bob"},
				},
			},
		},
		{"/keybase/private/alice#alice#alice", nil},
		{"/keybase/private/#alice", nil},
	}

	deepEq := func(a, b keybase1.ImplicitTeamName) bool {
		x, _ := libkb.MsgpackEncode(a)
		y, _ := libkb.MsgpackEncode(b)
		fmt.Printf("%s\n", hex.EncodeToString(x))
		fmt.Printf("%s\n", hex.EncodeToString(y))
		return bytes.Equal(x, y)
	}

	for _, test := range tests {
		itn, err := libkb.ParseImplicitTeamTLFName(MakeAssertionContext(), test.input)
		if test.output == nil {
			require.Error(t, err)
		} else {
			require.True(t, deepEq(itn, *test.output))
		}
	}
}

// TestParseImplicitTeamName is just a quick sanity check.
// quick sanity test -- mostly redundant with TLFName test above
func TestParseImplicitTeamName(t *testing.T) {
	goodName := "twitter:alice,bob@facebook,carol@keybase,dave"
	namePrivate, err := libkb.ParseImplicitTeamName(MakeAssertionContext(), goodName, false)
	require.NoError(t, err)
	namePublic, err := libkb.ParseImplicitTeamName(MakeAssertionContext(), goodName, true)
	require.NoError(t, err)
	require.True(t, namePrivate.IsPrivate)
	require.True(t, !namePublic.IsPrivate)
}
<commit_msg>fix externals parse<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package externals

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"testing"

	"github.com/stretchr/testify/require"

	libkb "github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol/keybase1"
)

func containsString(xs []string, target string) bool {
	for _, x := range xs {
		if x == target {
			return true
		}
	}
	return false
}

func TestParseImplicitTeamTLFName(t *testing.T) {
	badNames := []string{
		"foobar",
		"/keybas/public/foo,bar",
		"/keybase/publi/foo,bar",
		"/keybase/public/foobar,foo:@bar",
		"/keybase/public/foobar,foobar::",
		"/keybase/public/foobar,alice@fakemedia",
		"/keybase/public/foobar__underscore",
	}
	for _, badName := range badNames {
		_, err := libkb.ParseImplicitTeamTLFName(MakeAssertionContext(), badName)
		require.Error(t, err)
	}
	goodName := "/keybase/public/dave,twitter:alice,bob@facebook,carol@keybase,echo"
	name, err := libkb.ParseImplicitTeamTLFName(MakeAssertionContext(), goodName)
	require.NoError(t, err)
	require.Equal(t, name.IsPublic, true)
	require.Equal(t, len(name.Writers.KeybaseUsers), 3)
	require.Equal(t, len(name.Writers.UnresolvedUsers), 2)
	require.True(t, containsString(name.Writers.KeybaseUsers, "dave"))
	require.True(t, containsString(name.Writers.KeybaseUsers, "carol"))
	require.True(t, containsString(name.Writers.KeybaseUsers, "echo"))

	firstSocial := name.Writers.UnresolvedUsers[0]
	secondSocial := name.Writers.UnresolvedUsers[1]
	aliceExpected := keybase1.SocialAssertion{User: "alice", Service: keybase1.SocialAssertionService("twitter")}
	bobExpected := keybase1.SocialAssertion{User: "bob", Service: keybase1.SocialAssertionService("facebook")}
	require.True(t, firstSocial != secondSocial)
	require.True(t, firstSocial == aliceExpected || firstSocial == bobExpected)
	require.True(t, secondSocial == aliceExpected || secondSocial == bobExpected)
}

func TestPartImplicitTeamTLFNameEvenMore(t *testing.T) {
	tests := []struct {
		input  string
		output *keybase1.ImplicitTeamDisplayName
	}{
		{
			"/keybase/private/bob,alice#bob,alice",
			&keybase1.ImplicitTeamDisplayName{
				IsPublic: false,
				Writers: keybase1.ImplicitTeamUserSet{
					KeybaseUsers: []string{"alice", "bob"},
				},
			},
		},
		{
			"/keybase/private/bob,alice#bob,alice,doug,charlie",
			&keybase1.ImplicitTeamDisplayName{
				IsPublic: false,
				Writers: keybase1.ImplicitTeamUserSet{
					KeybaseUsers: []string{"alice", "bob"},
				},
				Readers: keybase1.ImplicitTeamUserSet{
					KeybaseUsers: []string{"charlie", "doug"},
				},
			},
		},
		{
			"/keybase/private/bob,alice,jason@github#bob,alice,doug,charlie,github:jason,keith@twitter,twitter:keith,beth@reddit,keith@github",
			&keybase1.ImplicitTeamDisplayName{
				IsPublic: false,
				Writers: keybase1.ImplicitTeamUserSet{
					KeybaseUsers: []string{"alice", "bob"},
					UnresolvedUsers: []keybase1.SocialAssertion{
						keybase1.SocialAssertion{
							User:    "jason",
							Service: keybase1.SocialAssertionService("github"),
						},
					},
				},
				Readers: keybase1.ImplicitTeamUserSet{
					KeybaseUsers: []string{"charlie", "doug"},
					UnresolvedUsers: []keybase1.SocialAssertion{
						keybase1.SocialAssertion{
							User:    "beth",
							Service: keybase1.SocialAssertionService("reddit"),
						},
						keybase1.SocialAssertion{
							User:    "keith",
							Service: keybase1.SocialAssertionService("github"),
						},
						keybase1.SocialAssertion{
							User:    "keith",
							Service: keybase1.SocialAssertionService("twitter"),
						},
					},
				},
			},
		},
		{
			"/keybase/private/keybase:alice,bob@keybase#bob,alice",
			&keybase1.ImplicitTeamDisplayName{
				IsPublic: false,
				Writers: keybase1.ImplicitTeamUserSet{
					KeybaseUsers: []string{"alice", "bob"},
				},
			},
		},
		{"/keybase/private/alice#alice#alice", nil},
		{"/keybase/private/#alice", nil},
	}

	deepEq := func(a, b keybase1.ImplicitTeamDisplayName) bool {
		x, _ := libkb.MsgpackEncode(a)
		y, _ := libkb.MsgpackEncode(b)
		fmt.Printf("%s\n", hex.EncodeToString(x))
		fmt.Printf("%s\n", hex.EncodeToString(y))
		return bytes.Equal(x, y)
	}

	for _, test := range tests {
		itn, err := libkb.ParseImplicitTeamTLFName(MakeAssertionContext(), test.input)
		if test.output == nil {
			require.Error(t, err)
		} else {
			require.True(t, deepEq(itn, *test.output))
		}
	}
}

// TestParseImplicitTeamDisplayName is just a quick sanity check.
// quick sanity test -- mostly redundant with TLFName test above
func TestParseImplicitTeamDisplayName(t *testing.T) {
	goodName := "twitter:alice,bob@facebook,carol@keybase,dave"
	namePrivate, err := libkb.ParseImplicitTeamDisplayName(MakeAssertionContext(), goodName, false)
	require.NoError(t, err)
	namePublic, err := libkb.ParseImplicitTeamDisplayName(MakeAssertionContext(), goodName, true)
	require.NoError(t, err)
	require.False(t, namePrivate.IsPublic)
	require.True(t, namePublic.IsPublic)
}
<|endoftext|>


<commit_before>package dal

import (
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/jmoiron/sqlx"
	sqlx_types "github.com/jmoiron/sqlx/types"
	"github.com/nytlabs/gojsonexplode"

	"github.com/resourced/resourced-master/querybuilder"
)

func NewHost(db *sqlx.DB) *Host {
	host := &Host{}
	host.db = db
	host.table = "hosts"
	host.hasID = true

	return host
}

type AgentResourcePayload struct {
	Data     map[string]interface{}
	GoStruct string `json:",omitempty"`
	Host     struct {
		Name string
		Tags map[string]string
	}
	Interval string `json:",omitempty"`
	Path     string
	UnixNano float64 `json:",omitempty"`
}

type HostRowsWithError struct {
	Hosts []*HostRow
	Error error
}

type HostRow struct {
	ID            int64               `db:"id" json:"-"`
	AccessTokenID int64               `db:"access_token_id" json:"-"`
	ClusterID     int64               `db:"cluster_id"`
	Hostname      string              `db:"hostname"`
	Updated       time.Time           `db:"updated"`
	Tags          sqlx_types.JSONText `db:"tags"`
	Data          sqlx_types.JSONText `db:"data"`
}

func (h *HostRow) GetTags() map[string]string {
	tags := make(map[string]string)
	h.Tags.Unmarshal(&tags)

	return tags
}

func (h *HostRow) GetClusterID() int64 {
	return h.ClusterID
}

func (h *HostRow) GetHostname() string {
	return h.Hostname
}

func (h *HostRow) DataAsFlatKeyValue() map[string]map[string]interface{} {
	inputData := make(map[string]map[string]interface{})

	outputData := make(map[string]map[string]interface{})

	err := json.Unmarshal(h.Data, &inputData)
	if err != nil {
		return outputData
	}

	for path, innerData := range inputData {
		innerDataJson, err := json.Marshal(innerData)
		if err != nil {
			continue
		}

		flattenMapJson, err := gojsonexplode.Explodejson(innerDataJson, ".")
		if err != nil {
			continue
		}

		flattenMap := make(map[string]interface{})

		err = json.Unmarshal(flattenMapJson, &flattenMap)
		if err != nil {
			continue
		}
		outputData[path] = flattenMap
	}

	return outputData
}

type Host struct {
	Base
}

func (h *Host) hostRowFromSqlResult(tx *sqlx.Tx, sqlResult sql.Result) (*HostRow, error) {
	hostId, err := sqlResult.LastInsertId()
	if err != nil {
		return nil, err
	}

	return h.GetByID(tx, hostId)
}

// AllByClusterID returns all rows.
func (h *Host) AllByClusterID(tx *sqlx.Tx, clusterID int64) ([]*HostRow, error) {
	hosts := []*HostRow{}
	query := fmt.Sprintf("SELECT * FROM %v WHERE cluster_id=$1 ORDER BY updated DESC", h.table)
	err := h.db.Select(&hosts, query, clusterID)

	return hosts, err
}

// CountByClusterIDAndUpdatedInterval returns the count of all rows.
func (h *Host) CountByClusterIDAndUpdatedInterval(tx *sqlx.Tx, clusterID int64, updatedInterval string) (int, error) {
	query := fmt.Sprintf("SELECT COUNT(*) as count FROM %v WHERE cluster_id=$1 AND updated >= (NOW() at time zone 'utc' - INTERVAL '%v')", h.table, updatedInterval)
	rows, err := h.db.Query(query, clusterID)
	if err != nil {
		return -1, err
	}

	var count int

	for rows.Next() {
		err := rows.Scan(&count)
		if err != nil {
			return -1, err
		}
	}

	return count, nil
}

// AllByClusterIDAndUpdatedInterval returns all rows.
func (h *Host) AllByClusterIDAndUpdatedInterval(tx *sqlx.Tx, clusterID int64, updatedInterval string) ([]*HostRow, error) {
	hosts := []*HostRow{}
	query := fmt.Sprintf("SELECT * FROM %v WHERE cluster_id=$1 AND updated >= (NOW() at time zone 'utc' - INTERVAL '%v')", h.table, updatedInterval)
	err := h.db.Select(&hosts, query, clusterID)

	return hosts, err
}

// AllByClusterIDAndQuery returns all rows by resourced query.
func (h *Host) AllByClusterIDAndQuery(tx *sqlx.Tx, clusterID int64, resourcedQuery string) ([]*HostRow, error) {
	pgQuery := querybuilder.Parse(resourcedQuery)
	if pgQuery == "" {
		return h.AllByClusterID(tx, clusterID)
	}

	hosts := []*HostRow{}
	query := fmt.Sprintf("SELECT * FROM %v WHERE cluster_id=$1 AND %v", h.table, pgQuery)
	err := h.db.Select(&hosts, query, clusterID)

	return hosts, err
}

// AllByClusterIDQueryAndUpdatedInterval returns all rows by resourced query.
func (h *Host) AllByClusterIDQueryAndUpdatedInterval(tx *sqlx.Tx, clusterID int64, resourcedQuery, updatedInterval string) ([]*HostRow, error) {
	pgQuery := querybuilder.Parse(resourcedQuery)
	if pgQuery == "" {
		return h.AllByClusterIDAndUpdatedInterval(tx, clusterID, updatedInterval)
	}

	hosts := []*HostRow{}
	query := fmt.Sprintf("SELECT * FROM %v WHERE cluster_id=$1 AND updated >= (NOW() at time zone 'utc' - INTERVAL '%v') AND %v", h.table, updatedInterval, pgQuery)
	err := h.db.Select(&hosts, query, clusterID)

	return hosts, err
}

// AllByClusterIDAndHostnames returns all rows by hostnames.
func (h *Host) AllByClusterIDAndHostnames(tx *sqlx.Tx, clusterID int64, hostnames []string) ([]*HostRow, error) {
	inPlaceHolders := make([]string, len(hostnames))

	for i := 0; i < len(hostnames); i++ {
		inPlaceHolders[i] = fmt.Sprintf("$%v", i+2)
	}

	hosts := []*HostRow{}

	query := fmt.Sprintf("SELECT * FROM %v WHERE cluster_id=$1 AND hostname IN (%v)", h.table, strings.Join(inPlaceHolders, ","))

	args := make([]interface{}, len(hostnames)+1)
	args[0] = clusterID

	for i := 1; i < len(hostnames)+1; i++ {
		args[i] = hostnames[i-1]
	}

	err := h.db.Select(&hosts, query, args...)

	return hosts, err
}

// GetByID returns record by id.
func (h *Host) GetByID(tx *sqlx.Tx, id int64) (*HostRow, error) {
	hostRow := &HostRow{}
	query := fmt.Sprintf("SELECT * FROM %v WHERE id=$1", h.table)
	err := h.db.Get(hostRow, query, id)

	return hostRow, err
}

// GetByHostname returns record by hostname.
func (h *Host) GetByHostname(tx *sqlx.Tx, hostname string) (*HostRow, error) {
	hostRow := &HostRow{}
	query := fmt.Sprintf("SELECT * FROM %v WHERE hostname=$1", h.table)
	err := h.db.Get(hostRow, query, hostname)

	return hostRow, err
}

func (h *Host) parseAgentResourcePayload(tx *sqlx.Tx, accessTokenRow *AccessTokenRow, jsonData []byte) (map[string]interface{}, error) {
	resourcedPayloads := make(map[string]*AgentResourcePayload)

	err := json.Unmarshal(jsonData, &resourcedPayloads)
	if err != nil {
		return nil, err
	}

	resourcedPayloadJustData := make(map[string]map[string]interface{})

	data := make(map[string]interface{})
	data["access_token_id"] = accessTokenRow.ID
	data["cluster_id"] = accessTokenRow.ClusterID

	for path, resourcedPayload := range resourcedPayloads {
		data["hostname"] = resourcedPayload.Host.Name

		tagsInJson, err := json.Marshal(resourcedPayload.Host.Tags)
		if err != nil {
			continue
		}
		data["tags"] = tagsInJson

		resourcedPayloadJustData[path] = resourcedPayload.Data
	}

	resourcedPayloadJustJson, err := json.Marshal(resourcedPayloadJustData)
	if err != nil {
		return nil, err
	}

	data["data"] = resourcedPayloadJustJson

	return data, nil
}

// CreateOrUpdate performs insert/update for one host data.
func (h *Host) CreateOrUpdate(tx *sqlx.Tx, accessTokenRow *AccessTokenRow, jsonData []byte) (*HostRow, error) {
	data, err := h.parseAgentResourcePayload(tx, accessTokenRow, jsonData)
	if err != nil {
		return nil, err
	}

	if data["hostname"] == nil {
		return nil, errors.New("Hostname cannot be empty.")
	}

	hostRow, err := h.GetByHostname(tx, data["hostname"].(string))

	// Perform INSERT
	if hostRow == nil || err != nil {
		sqlResult, err := h.InsertIntoTable(tx, data)
		if err != nil {
			return nil, err
		}

		return h.hostRowFromSqlResult(tx, sqlResult)
	}

	if _, ok := data["updated"]; !ok {
		data["updated"] = time.Now().UTC()
	}

	// Perform UPDATE
	_, err = h.UpdateByKeyValueString(tx, data, "hostname", data["hostname"].(string))
	if err != nil {
		return nil, err
	}

	return hostRow, nil
}
<commit_msg>Able to receive flatten host data from agent.<commit_after>package dal

import (
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/jmoiron/sqlx"
	sqlx_types "github.com/jmoiron/sqlx/types"
	"github.com/nytlabs/gojsonexplode"

	"github.com/resourced/resourced-master/querybuilder"
)

func NewHost(db *sqlx.DB) *Host {
	host := &Host{}
	host.db = db
	host.table = "hosts"
	host.hasID = true

	return host
}

type AgentResourcePayload struct {
	Data map[string]interface{}
	Host struct {
		Name string
		Tags map[string]string
	}
}

type HostRowsWithError struct {
	Hosts []*HostRow
	Error error
}

type HostRow struct {
	ID            int64               `db:"id" json:"-"`
	AccessTokenID int64               `db:"access_token_id" json:"-"`
	ClusterID     int64               `db:"cluster_id"`
	Hostname      string              `db:"hostname"`
	Updated       time.Time           `db:"updated"`
	Tags          sqlx_types.JSONText `db:"tags"`
	Data          sqlx_types.JSONText `db:"data"`
}

func (h *HostRow) GetTags() map[string]string {
	tags := make(map[string]string)
	h.Tags.Unmarshal(&tags)

	return tags
}

func (h *HostRow) GetClusterID() int64 {
	return h.ClusterID
}

func (h *HostRow) GetHostname() string {
	return h.Hostname
}

func (h *HostRow) DataAsFlatKeyValue() map[string]map[string]interface{} {
	inputData := make(map[string]map[string]interface{})

	outputData := make(map[string]map[string]interface{})

	err := json.Unmarshal(h.Data, &inputData)
	if err != nil {
		return outputData
	}

	for path, innerData := range inputData {
		innerDataJson, err := json.Marshal(innerData)
		if err != nil {
			continue
		}

		flattenMapJson, err := gojsonexplode.Explodejson(innerDataJson, ".")
		if err != nil {
			continue
		}

		flattenMap := make(map[string]interface{})

		err = json.Unmarshal(flattenMapJson, &flattenMap)
		if err != nil {
			continue
		}
		outputData[path] = flattenMap
	}

	return outputData
}

type Host struct {
	Base
}

func (h *Host) hostRowFromSqlResult(tx *sqlx.Tx, sqlResult sql.Result) (*HostRow, error) {
	hostId, err := sqlResult.LastInsertId()
	if err != nil {
		return nil, err
	}

	return h.GetByID(tx, hostId)
}

// AllByClusterID returns all rows.
func (h *Host) AllByClusterID(tx *sqlx.Tx, clusterID int64) ([]*HostRow, error) {
	hosts := []*HostRow{}
	query := fmt.Sprintf("SELECT * FROM %v WHERE cluster_id=$1 ORDER BY updated DESC", h.table)
	err := h.db.Select(&hosts, query, clusterID)

	return hosts, err
}

// CountByClusterIDAndUpdatedInterval returns the count of all rows.
func (h *Host) CountByClusterIDAndUpdatedInterval(tx *sqlx.Tx, clusterID int64, updatedInterval string) (int, error) {
	query := fmt.Sprintf("SELECT COUNT(*) as count FROM %v WHERE cluster_id=$1 AND updated >= (NOW() at time zone 'utc' - INTERVAL '%v')", h.table, updatedInterval)
	rows, err := h.db.Query(query, clusterID)
	if err != nil {
		return -1, err
	}

	var count int

	for rows.Next() {
		err := rows.Scan(&count)
		if err != nil {
			return -1, err
		}
	}

	return count, nil
}

// AllByClusterIDAndUpdatedInterval returns all rows.
func (h *Host) AllByClusterIDAndUpdatedInterval(tx *sqlx.Tx, clusterID int64, updatedInterval string) ([]*HostRow, error) {
	hosts := []*HostRow{}
	query := fmt.Sprintf("SELECT * FROM %v WHERE cluster_id=$1 AND updated >= (NOW() at time zone 'utc' - INTERVAL '%v')", h.table, updatedInterval)
	err := h.db.Select(&hosts, query, clusterID)

	return hosts, err
}

// AllByClusterIDAndQuery returns all rows by resourced query.
func (h *Host) AllByClusterIDAndQuery(tx *sqlx.Tx, clusterID int64, resourcedQuery string) ([]*HostRow, error) {
	pgQuery := querybuilder.Parse(resourcedQuery)
	if pgQuery == "" {
		return h.AllByClusterID(tx, clusterID)
	}

	hosts := []*HostRow{}
	query := fmt.Sprintf("SELECT * FROM %v WHERE cluster_id=$1 AND %v", h.table, pgQuery)
	err := h.db.Select(&hosts, query, clusterID)

	return hosts, err
}

// AllByClusterIDQueryAndUpdatedInterval returns all rows by resourced query.
func (h *Host) AllByClusterIDQueryAndUpdatedInterval(tx *sqlx.Tx, clusterID int64, resourcedQuery, updatedInterval string) ([]*HostRow, error) {
	pgQuery := querybuilder.Parse(resourcedQuery)
	if pgQuery == "" {
		return h.AllByClusterIDAndUpdatedInterval(tx, clusterID, updatedInterval)
	}

	hosts := []*HostRow{}
	query := fmt.Sprintf("SELECT * FROM %v WHERE cluster_id=$1 AND updated >= (NOW() at time zone 'utc' - INTERVAL '%v') AND %v", h.table, updatedInterval, pgQuery)
	err := h.db.Select(&hosts, query, clusterID)

	return hosts, err
}

// AllByClusterIDAndHostnames returns all rows by hostnames.
func (h *Host) AllByClusterIDAndHostnames(tx *sqlx.Tx, clusterID int64, hostnames []string) ([]*HostRow, error) {
	inPlaceHolders := make([]string, len(hostnames))

	for i := 0; i < len(hostnames); i++ {
		inPlaceHolders[i] = fmt.Sprintf("$%v", i+2)
	}

	hosts := []*HostRow{}

	query := fmt.Sprintf("SELECT * FROM %v WHERE cluster_id=$1 AND hostname IN (%v)", h.table, strings.Join(inPlaceHolders, ","))

	args := make([]interface{}, len(hostnames)+1)
	args[0] = clusterID

	for i := 1; i < len(hostnames)+1; i++ {
		args[i] = hostnames[i-1]
	}

	err := h.db.Select(&hosts, query, args...)

	return hosts, err
}

// GetByID returns record by id.
func (h *Host) GetByID(tx *sqlx.Tx, id int64) (*HostRow, error) {
	hostRow := &HostRow{}
	query := fmt.Sprintf("SELECT * FROM %v WHERE id=$1", h.table)
	err := h.db.Get(hostRow, query, id)

	return hostRow, err
}

// GetByHostname returns record by hostname.
func (h *Host) GetByHostname(tx *sqlx.Tx, hostname string) (*HostRow, error) {
	hostRow := &HostRow{}
	query := fmt.Sprintf("SELECT * FROM %v WHERE hostname=$1", h.table)
	err := h.db.Get(hostRow, query, hostname)

	return hostRow, err
}

func (h *Host) parseAgentResourcePayload(tx *sqlx.Tx, accessTokenRow *AccessTokenRow, jsonData []byte) (map[string]interface{}, error) {
	resourcedPayload := AgentResourcePayload{}

	err := json.Unmarshal(jsonData, &resourcedPayload)
	if err != nil {
		return nil, err
	}

	data := make(map[string]interface{})
	data["access_token_id"] = accessTokenRow.ID
	data["cluster_id"] = accessTokenRow.ClusterID
	data["hostname"] = resourcedPayload.Host.Name

	tagsInJson, err := json.Marshal(resourcedPayload.Host.Tags)
	if err != nil {
		return nil, err
	}
	data["tags"] = tagsInJson

	resourcedPayloadJustJson, err := json.Marshal(resourcedPayload.Data)
	if err != nil {
		return nil, err
	}

	data["data"] = resourcedPayloadJustJson

	return data, nil
}

// CreateOrUpdate performs insert/update for one host data.
func (h *Host) CreateOrUpdate(tx *sqlx.Tx, accessTokenRow *AccessTokenRow, jsonData []byte) (*HostRow, error) {
	data, err := h.parseAgentResourcePayload(tx, accessTokenRow, jsonData)
	if err != nil {
		return nil, err
	}

	if data["hostname"] == nil {
		return nil, errors.New("Hostname cannot be empty.")
	}

	hostRow, err := h.GetByHostname(tx, data["hostname"].(string))

	// Perform INSERT
	if hostRow == nil || err != nil {
		sqlResult, err := h.InsertIntoTable(tx, data)
		if err != nil {
			return nil, err
		}

		return h.hostRowFromSqlResult(tx, sqlResult)
	}

	if _, ok := data["updated"]; !ok {
		data["updated"] = time.Now().UTC()
	}

	// Perform UPDATE
	_, err = h.UpdateByKeyValueString(tx, data, "hostname", data["hostname"].(string))
	if err != nil {
		return nil, err
	}

	return hostRow, nil
}
<|endoftext|>


<commit_before>package models

import (
	"fmt"
	"socialapi/request"
	"strings"

	"github.com/koding/bongo"
)

type Account struct {
	// unique id of the account
	Id int64 `json:"id,string"`

	// old id of the account, which is originally
	// perisisted in mongo
	// mongo ids has 24 char
	OldId string `json:"oldId"      sql:"NOT NULL;UNIQUE;TYPE:VARCHAR(24);"`

	// IsTroll
	IsTroll bool `json:"isTroll"`

	// unique account nicknames
	Nick string `json:"nick"        sql:"NOT NULL;UNIQUE;TYPE:VARCHAR(25);"`
}

// Tests are done.
func (a *Account) FetchOrCreate() error {
	if a.OldId == "" {
		return ErrOldIdIsNotSet
	}

	if strings.Contains(a.Nick, "guest-") {
		return ErrGuestsAreNotAllowed
	}

	selector := map[string]interface{}{
		"old_id": a.OldId,
	}

	err := a.One(bongo.NewQS(selector))
	// if we dont get any error
	// it means we found the record in our db
	if err == nil {
		return nil
	}

	// first check if the err is not found err
	if err == bongo.RecordNotFound {
		if a.Nick == "" {
			return ErrNickIsNotSet
		}

		if err := a.Create(); err != nil {
			return err
		}

		return nil
	}

	return err
}

func (a *Account) FetchChannels(q *request.Query) ([]Channel, error) {
	cp := NewChannelParticipant()
	// fetch channel ids
	cids, err := cp.FetchParticipatedChannelIds(a, q)
	if err != nil {
		return nil, err
	}

	// fetch channels by their ids
	channels, err := NewChannel().FetchByIds(cids)
	if err != nil {
		return nil, err
	}

	return channels, nil
}

// TO-DO
// Control functions and remove ?
func (a *Account) Follow(targetId int64) (*ChannelParticipant, error) {
	c, err := a.FetchChannel(Channel_TYPE_FOLLOWERS)
	if err == nil {
		return c.AddParticipant(targetId)
	}

	if err == bongo.RecordNotFound {
		c, err := a.CreateFollowingFeedChannel()
		if err != nil {
			return nil, err
		}
		return c.AddParticipant(targetId)
	}

	return nil, err
}

// TO-DO
// Control functions and remove ?
func (a *Account) Unfollow(targetId int64) (*Account, error) {
	c, err := a.FetchChannel(Channel_TYPE_FOLLOWERS)
	if err != nil {
		return nil, err
	}

	return a, c.RemoveParticipant(targetId)
}

func (a *Account) FetchFollowerIds(q *request.Query) ([]int64, error) {
	followerIds := make([]int64, 0)
	if a.Id == 0 {
		return nil, ErrAccountIdIsNotSet
	}

	c, err := a.FetchChannel(Channel_TYPE_FOLLOWERS)
	if err != nil {
		return followerIds, err
	}

	participants, err := c.FetchParticipantIds(q)
	if err != nil {
		return followerIds, err
	}

	return participants, nil
}

// FetchChannel fetchs the channel of the account
//
// Channel_TYPE_GROUP as parameter returns error , in the tests!!!!
// TO-DO, other types dont return error
//
// Tests are done
func (a *Account) FetchChannel(channelType string) (*Channel, error) {
	if a.Id == 0 {
		return nil, ErrAccountIdIsNotSet
	}

	c := NewChannel()
	selector := map[string]interface{}{
		"creator_id":    a.Id,
		"type_constant": channelType,
	}

	if err := c.One(bongo.NewQS(selector)); err != nil {
		return nil, err
	}

	return c, nil
}

// Tests are done.
func (a *Account) MarkAsTroll() error {
	if a.Id == 0 {
		return ErrAccountIdIsNotSet
	}

	if err := a.ById(a.Id); err != nil {
		return err
	}

	// do not try to mark twice
	if a.IsTroll {
		return fmt.Errorf("account is already a troll %d", a.Id)
	}

	a.IsTroll = true
	if err := a.Update(); err != nil {
		return err
	}

	if err := bongo.B.PublishEvent("marked_as_troll", a); err != nil {
		return err
	}

	return nil
}

// Tests are done
func (a *Account) UnMarkAsTroll() error {
	if a.Id == 0 {
		return ErrAccountIdIsNotSet
	}

	if err := a.ById(a.Id); err != nil {
		return err
	}

	// do not try to un-mark twice
	if !a.IsTroll {
		return fmt.Errorf("account is not a troll %d", a.Id)
	}

	a.IsTroll = false
	if err := a.Update(); err != nil {
		return err
	}

	if err := bongo.B.PublishEvent("unmarked_as_troll", a); err != nil {
		return err
	}

	return nil
}

// Tests are done.
func (a *Account) CreateFollowingFeedChannel() (*Channel, error) {
	if a.Id == 0 {
		return nil, ErrAccountIdIsNotSet
	}

	c := NewChannel()
	c.CreatorId = a.Id
	c.Name = fmt.Sprintf("%d-FollowingFeedChannel", a.Id)
	c.GroupName = Channel_KODING_NAME
	c.Purpose = "Following Feed for Me"
	c.TypeConstant = Channel_TYPE_FOLLOWERS
	if err := c.Create(); err != nil {
		return nil, err
	}

	return c, nil
}

func (a *Account) FetchFollowerChannelIds(q *request.Query) ([]int64, error) {

	followerIds, err := a.FetchFollowerIds(q)
	if err != nil {
		return nil, err
	}

	cp := NewChannelParticipant()
	var channelIds []int64
	res := bongo.B.DB.
		Table(cp.TableName()).
		Where(
		"creator_id IN (?) and type_constant = ?",
		followerIds,
		Channel_TYPE_FOLLOWINGFEED,
	).Find(&channelIds)

	if err := bongo.CheckErr(res); err != nil {
		return nil, err
	}

	return channelIds, nil
}

// FetchAccountById gives all information about account by id of account
//
// Tests are done.
func FetchAccountById(accountId int64) (*Account, error) {
	a := NewAccount()
	if err := a.ById(accountId); err != nil {
		return nil, err
	}

	return a, nil
}

// FetchOldIdsByAccountIds takes slice as parameter
//
// Tests are done
func FetchOldIdsByAccountIds(accountIds []int64) ([]string, error) {
	oldIds := make([]string, 0)

	if len(accountIds) == 0 {
		return oldIds, nil
	}

	var accounts []Account
	account := Account{}

	err := bongo.B.FetchByIds(account, &accounts, accountIds)
	if err != nil {
		return oldIds, nil
	}

	for _, account := range accounts {
		// The append built-in function appends elements to the end of a slice
		oldIds = append(oldIds, account.OldId)
	}

	return oldIds, nil
}
<commit_msg>Social: add ByNick function into Account model<commit_after>package models

import (
	"fmt"
	"socialapi/request"
	"strings"

	"github.com/koding/bongo"
)

type Account struct {
	// unique id of the account
	Id int64 `json:"id,string"`

	// old id of the account, which is originally
	// perisisted in mongo
	// mongo ids has 24 char
	OldId string `json:"oldId"      sql:"NOT NULL;UNIQUE;TYPE:VARCHAR(24);"`

	// IsTroll
	IsTroll bool `json:"isTroll"`

	// unique account nicknames
	Nick string `json:"nick"        sql:"NOT NULL;UNIQUE;TYPE:VARCHAR(25);"`
}

// Tests are done.
func (a *Account) FetchOrCreate() error {
	if a.OldId == "" {
		return ErrOldIdIsNotSet
	}

	if strings.Contains(a.Nick, "guest-") {
		return ErrGuestsAreNotAllowed
	}

	selector := map[string]interface{}{
		"old_id": a.OldId,
	}

	err := a.One(bongo.NewQS(selector))
	// if we dont get any error
	// it means we found the record in our db
	if err == nil {
		return nil
	}

	// first check if the err is not found err
	if err == bongo.RecordNotFound {
		if a.Nick == "" {
			return ErrNickIsNotSet
		}

		if err := a.Create(); err != nil {
			return err
		}

		return nil
	}

	return err
}

func (a *Account) FetchChannels(q *request.Query) ([]Channel, error) {
	cp := NewChannelParticipant()
	// fetch channel ids
	cids, err := cp.FetchParticipatedChannelIds(a, q)
	if err != nil {
		return nil, err
	}

	// fetch channels by their ids
	channels, err := NewChannel().FetchByIds(cids)
	if err != nil {
		return nil, err
	}

	return channels, nil
}

// TO-DO
// Control functions and remove ?
func (a *Account) Follow(targetId int64) (*ChannelParticipant, error) {
	c, err := a.FetchChannel(Channel_TYPE_FOLLOWERS)
	if err == nil {
		return c.AddParticipant(targetId)
	}

	if err == bongo.RecordNotFound {
		c, err := a.CreateFollowingFeedChannel()
		if err != nil {
			return nil, err
		}
		return c.AddParticipant(targetId)
	}

	return nil, err
}

// TO-DO
// Control functions and remove ?
func (a *Account) Unfollow(targetId int64) (*Account, error) {
	c, err := a.FetchChannel(Channel_TYPE_FOLLOWERS)
	if err != nil {
		return nil, err
	}

	return a, c.RemoveParticipant(targetId)
}

func (a *Account) FetchFollowerIds(q *request.Query) ([]int64, error) {
	followerIds := make([]int64, 0)
	if a.Id == 0 {
		return nil, ErrAccountIdIsNotSet
	}

	c, err := a.FetchChannel(Channel_TYPE_FOLLOWERS)
	if err != nil {
		return followerIds, err
	}

	participants, err := c.FetchParticipantIds(q)
	if err != nil {
		return followerIds, err
	}

	return participants, nil
}

// FetchChannel fetchs the channel of the account
//
// Channel_TYPE_GROUP as parameter returns error , in the tests!!!!
// TO-DO, other types dont return error
//
// Tests are done
func (a *Account) FetchChannel(channelType string) (*Channel, error) {
	if a.Id == 0 {
		return nil, ErrAccountIdIsNotSet
	}

	c := NewChannel()
	selector := map[string]interface{}{
		"creator_id":    a.Id,
		"type_constant": channelType,
	}

	if err := c.One(bongo.NewQS(selector)); err != nil {
		return nil, err
	}

	return c, nil
}

// todo add test
func (a *Account) ByNick(nick string) error {
	if nick == "" {
		return ErrNickIsNotSet
	}

	selector := map[string]interface{}{
		"nick": nick,
	}

	return a.One(bongo.NewQS(selector))
}

// Tests are done.
func (a *Account) MarkAsTroll() error {
	if a.Id == 0 {
		return ErrAccountIdIsNotSet
	}

	if err := a.ById(a.Id); err != nil {
		return err
	}

	// do not try to mark twice
	if a.IsTroll {
		return fmt.Errorf("account is already a troll %d", a.Id)
	}

	a.IsTroll = true
	if err := a.Update(); err != nil {
		return err
	}

	if err := bongo.B.PublishEvent("marked_as_troll", a); err != nil {
		return err
	}

	return nil
}

// Tests are done
func (a *Account) UnMarkAsTroll() error {
	if a.Id == 0 {
		return ErrAccountIdIsNotSet
	}

	if err := a.ById(a.Id); err != nil {
		return err
	}

	// do not try to un-mark twice
	if !a.IsTroll {
		return fmt.Errorf("account is not a troll %d", a.Id)
	}

	a.IsTroll = false
	if err := a.Update(); err != nil {
		return err
	}

	if err := bongo.B.PublishEvent("unmarked_as_troll", a); err != nil {
		return err
	}

	return nil
}

// Tests are done.
func (a *Account) CreateFollowingFeedChannel() (*Channel, error) {
	if a.Id == 0 {
		return nil, ErrAccountIdIsNotSet
	}

	c := NewChannel()
	c.CreatorId = a.Id
	c.Name = fmt.Sprintf("%d-FollowingFeedChannel", a.Id)
	c.GroupName = Channel_KODING_NAME
	c.Purpose = "Following Feed for Me"
	c.TypeConstant = Channel_TYPE_FOLLOWERS
	if err := c.Create(); err != nil {
		return nil, err
	}

	return c, nil
}

func (a *Account) FetchFollowerChannelIds(q *request.Query) ([]int64, error) {

	followerIds, err := a.FetchFollowerIds(q)
	if err != nil {
		return nil, err
	}

	cp := NewChannelParticipant()
	var channelIds []int64
	res := bongo.B.DB.
		Table(cp.TableName()).
		Where(
		"creator_id IN (?) and type_constant = ?",
		followerIds,
		Channel_TYPE_FOLLOWINGFEED,
	).Find(&channelIds)

	if err := bongo.CheckErr(res); err != nil {
		return nil, err
	}

	return channelIds, nil
}

// FetchAccountById gives all information about account by id of account
//
// Tests are done.
func FetchAccountById(accountId int64) (*Account, error) {
	a := NewAccount()
	if err := a.ById(accountId); err != nil {
		return nil, err
	}

	return a, nil
}

// FetchOldIdsByAccountIds takes slice as parameter
//
// Tests are done
func FetchOldIdsByAccountIds(accountIds []int64) ([]string, error) {
	oldIds := make([]string, 0)

	if len(accountIds) == 0 {
		return oldIds, nil
	}

	var accounts []Account
	account := Account{}

	err := bongo.B.FetchByIds(account, &accounts, accountIds)
	if err != nil {
		return oldIds, nil
	}

	for _, account := range accounts {
		// The append built-in function appends elements to the end of a slice
		oldIds = append(oldIds, account.OldId)
	}

	return oldIds, nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"strconv"
	"time"
	"encoding/json"
	"net/http"
)

type Event struct {
	name      string // "prefix_event"
	timestamp time.Time
}

func newEvent(n string) (e *Event) {
	e = new(Event)

	e.name = n
	e.timestamp = time.Now()

	return e
}

type Callback struct {
	event string // "prefix_event"
	addr  string // "http://1.2.3.4:56/ev/"
}

func newCallback(e string, a string) (c *Callback) {
	c = new(Callback)

	c.event = e
	c.addr = a

	return c
}

var callbacks = map[string]([]*Callback) {
	// Global map to hold all registered callbacks sorted by the
	// callback's name.
}

func main() {
	port := flag.Int("port", 8080, "Port to listen on")
	flag.Parse()

	http.HandleFunc("/status/", statusHandler)
	http.HandleFunc("/api/v1/", apiV1Handler)
	http.ListenAndServe(":" + strconv.Itoa(*port), nil)
}

// We use handlers that just write the return of the corrsoponding
// functions, which makes testing a lot simpler.

func statusHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte(status()))
}

func status() string {
	return ""
}

// V1 of the general API - handles everything that will be used by
// other services.
func apiV1Handler(w http.ResponseWriter, r *http.Request) {
	if r.Method == "POST" { // POST for registering a new callback
		type V1Request struct {
			Action, Event, Addr string
		}

		dec := json.NewDecoder(r.Body)
		var rq V1Request
		err := dec.Decode(&rq)
		if err != nil {
			panic(err)
		}

		if rq.Action == "register" {
			registerCallback(rq.Event, rq.Addr)
		}
	}
}

func registerCallback(n string, a string) {
	c := newCallback(n, a)
	callbacks[n] = append(callbacks[n], c)
}

<commit_msg>fix a critical issue which caused new callbacks to use a wrong address<commit_after>package main

import (
	"flag"
	"strconv"
	"time"
	"encoding/json"
	"net/http"
)

type Event struct {
	name      string // "prefix_event"
	timestamp time.Time
}

func newEvent(n string) (e *Event) {
	e = new(Event)

	e.name = n
	e.timestamp = time.Now()

	return e
}

type Callback struct {
	event string // "prefix_event"
	addr  string // "http://1.2.3.4:56/ev/"
}

func newCallback(e string, a string) (c *Callback) {
	c = new(Callback)

	c.event = e
	c.addr = a

	return c
}

var callbacks = map[string]([]*Callback) {
	// Global map to hold all registered callbacks sorted by the
	// callback's name.
}

func main() {
	port := flag.Int("port", 8080, "Port to listen on")
	flag.Parse()

	http.HandleFunc("/status/", statusHandler)
	http.HandleFunc("/api/v1/", apiV1Handler)
	http.ListenAndServe(":" + strconv.Itoa(*port), nil)
}

// We use handlers that just write the return of the corrsoponding
// functions, which makes testing a lot simpler.

func statusHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte(status()))
}

func status() string {
	return ""
}

// V1 of the general API - handles everything that will be used by
// other services.
func apiV1Handler(w http.ResponseWriter, r *http.Request) {
	if r.Method == "POST" { // POST for registering a new callback
		type V1Request struct {
			Action, Event, Addr string
		}

		dec := json.NewDecoder(r.Body)
		var rq V1Request
		err := dec.Decode(&rq)
		if err != nil {
			panic(err)
		}

		if rq.Action == "register" {
			registerCallback(rq.Event, r.RemoteAddr)
		}
	}
}

func registerCallback(n string, a string) {
	c := newCallback(n, a)
	callbacks[n] = append(callbacks[n], c)
}

<|endoftext|>


<commit_before>package models

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/jinzhu/gorm"
	"github.com/koding/bongo"
)

type Channel struct {
	// unique identifier of the channel
	Id int64 `json:"id"`

	// Name of the channel
	Name string `json:"name"                   sql:"NOT NULL;TYPE:VARCHAR(200);"`

	// Creator of the channel
	CreatorId int64 `json:"creatorId"          sql:"NOT NULL"`

	// Name of the group which channel is belong to
	GroupName string `json:"groupName"         sql:"NOT NULL;TYPE:VARCHAR(200);"`

	// Purpose of the channel
	Purpose string `json:"purpose"`

	// Secret key of the channel for event propagation purposes
	// we can put this key into another table?
	SecretKey string `json:"secretKey"`

	// Type of the channel
	TypeConstant string `json:"typeConstant"   sql:"NOT NULL"`

	// Privacy constant of the channel
	Privacy string `json:"privacy"             sql:"NOT NULL"`

	// Creation date of the channel
	CreatedAt time.Time `json:"createdAt"      sql:"NOT NULL"`

	// Modification date of the channel
	UpdatedAt time.Time `json:"updatedAt"      sql:"NOT NULL"`
}

// to-do check for allowed channels
const (
	// TYPES
	Channel_TYPE_GROUP         = "group"
	Channel_TYPE_TOPIC         = "topic"
	Channel_TYPE_FOLLOWINGFEED = "followingfeed"
	Channel_TYPE_FOLLOWERS     = "followers"
	Channel_TYPE_CHAT          = "chat"
	// Privacy
	Channel_TYPE_PUBLIC  = "public"
	Channel_TYPE_PRIVATE = "private"
	// Koding Group Name
	Channel_KODING_NAME = "koding"
)

func NewChannel() *Channel {
	return &Channel{
		Name:      "koding",
		CreatorId: 123,
		GroupName: Channel_KODING_NAME,
		Purpose:   "string",
		SecretKey: "string",
		Type:      Channel_TYPE_GROUP,
		Privacy:   Channel_TYPE_PRIVATE,
	}
}

func (c *Channel) BeforeCreate() {
	c.CreatedAt = time.Now()
	c.UpdatedAt = time.Now()
}

func (c *Channel) BeforeUpdate() {
	c.UpdatedAt = time.Now()
}

func (c *Channel) GetId() int64 {
	return c.Id
}

func (c *Channel) TableName() string {
	return "channel"
}

func (c *Channel) Fetch() error {
	return bongo.B.Fetch(c)
}

func (c *Channel) AfterCreate() {
	bongo.B.AfterCreate(c)
}

func (c *Channel) AfterUpdate() {
	bongo.B.AfterUpdate(c)
}

func (c *Channel) AfterDelete() {
	bongo.B.AfterDelete(c)
}

func (c *Channel) Update() error {
	if c.Name == "" || c.GroupName == "" {
		return fmt.Errorf("Validation failed %s - %s", c.Name, c.GroupName)
	}

	return bongo.B.Update(c)
}

func (c *Channel) Create() error {
	if c.Name == "" || c.GroupName == "" || c.TypeConstant == "" {
		return fmt.Errorf("Validation failed %s - %s", c.Name, c.GroupName)
	}

	// golang returns -1 if item not in the string
	if strings.Index(c.Name, " ") > -1 {
		return fmt.Errorf("Channel name %q has empty space in it", c.Name)
	}

	selector := map[string]interface{}{
		"name":       c.Name,
		"group_name": c.GroupName,
	}

	// if err is nil
	// it means we already have that channel
	err := c.One(selector)
	if err == nil {
		return fmt.Errorf("Channel %s is already created before for %s group", c.Name, c.GroupName)
	}

	if err != gorm.RecordNotFound {
		return err
	}

	return bongo.B.Create(c)
}

func (c *Channel) Delete() error {
	return bongo.B.Delete(c)
}

func (c *Channel) One(selector map[string]interface{}) error {
	return bongo.B.One(c, c, selector)
}

func (c *Channel) FetchByIds(ids []int64) ([]Channel, error) {
	var channels []Channel

	if len(ids) == 0 {
		return channels, nil
	}

	if err := bongo.B.FetchByIds(c, &channels, ids); err != nil {
		return nil, err
	}
	return channels, nil
}

func (c *Channel) AddParticipant(participantId int64) (*ChannelParticipant, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel Id is not set")
	}

	cp := NewChannelParticipant()
	cp.ChannelId = c.Id
	cp.AccountId = participantId

	err := cp.FetchParticipant()
	if err != nil && err != gorm.RecordNotFound {
		return nil, err
	}

	// if we have this record in DB
	if cp.Id != 0 {
		// if status is not active
		if cp.Status == ChannelParticipant_STATUS_ACTIVE {
			return nil, errors.New(fmt.Sprintf("Account %s is already a participant of channel %s", cp.AccountId, cp.ChannelId))
		}
		cp.Status = ChannelParticipant_STATUS_ACTIVE
		if err := cp.Update(); err != nil {
			return nil, err
		}
		return cp, nil
	}

	cp.Status = ChannelParticipant_STATUS_ACTIVE

	if err := cp.Create(); err != nil {
		return nil, err
	}

	return cp, nil
}

func (c *Channel) RemoveParticipant(participantId int64) error {
	if c.Id == 0 {
		return errors.New("Channel Id is not set")
	}

	cp := NewChannelParticipant()
	cp.ChannelId = c.Id
	cp.AccountId = participantId

	err := cp.FetchParticipant()
	// if user is not in this channel, do nothing
	if err == gorm.RecordNotFound {
		return nil
	}

	if err != nil {
		return err
	}

	if cp.Status == ChannelParticipant_STATUS_LEFT {
		return nil
	}

	cp.Status = ChannelParticipant_STATUS_LEFT
	if err := cp.Update(); err != nil {
		return err
	}

	return nil
}

func (c *Channel) FetchParticipantIds() ([]int64, error) {
	var participantIds []int64

	if c.Id == 0 {
		return participantIds, errors.New("Channel Id is not set")
	}

	query := &bongo.Query{
		Selector: map[string]interface{}{
			"channel_id": c.Id,
			"status":     ChannelParticipant_STATUS_ACTIVE,
		},
		Pluck: "account_id",
	}

	cp := NewChannelParticipant()
	err := cp.Some(&participantIds, query)
	if err != nil {
		return nil, err
	}

	return participantIds, nil
}

func (c *Channel) AddMessage(messageId int64) (*ChannelMessageList, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel Id is not set")
	}

	cml := NewChannelMessageList()
	cml.ChannelId = c.Id
	cml.MessageId = messageId

	if err := cml.Create(); err != nil {
		return nil, err
	}

	return cml, nil
}

func (c *Channel) List(q *Query) ([]Channel, error) {

	var channels []Channel

	query := &bongo.Query{
		Selector: map[string]interface{}{
			"group_name": q.GroupName,
		},
	}

	err := bongo.B.Some(c, &channels, query)
	if err != nil {
		return nil, err
	}

	return channels, nil
}
<commit_msg>Social: fix default values<commit_after>package models

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/jinzhu/gorm"
	"github.com/koding/bongo"
)

type Channel struct {
	// unique identifier of the channel
	Id int64 `json:"id"`

	// Name of the channel
	Name string `json:"name"                   sql:"NOT NULL;TYPE:VARCHAR(200);"`

	// Creator of the channel
	CreatorId int64 `json:"creatorId"          sql:"NOT NULL"`

	// Name of the group which channel is belong to
	GroupName string `json:"groupName"         sql:"NOT NULL;TYPE:VARCHAR(200);"`

	// Purpose of the channel
	Purpose string `json:"purpose"`

	// Secret key of the channel for event propagation purposes
	// we can put this key into another table?
	SecretKey string `json:"secretKey"`

	// Type of the channel
	TypeConstant string `json:"typeConstant"   sql:"NOT NULL"`

	// Privacy constant of the channel
	Privacy string `json:"privacy"             sql:"NOT NULL"`

	// Creation date of the channel
	CreatedAt time.Time `json:"createdAt"      sql:"NOT NULL"`

	// Modification date of the channel
	UpdatedAt time.Time `json:"updatedAt"      sql:"NOT NULL"`
}

// to-do check for allowed channels
const (
	// TYPES
	Channel_TYPE_GROUP         = "group"
	Channel_TYPE_TOPIC         = "topic"
	Channel_TYPE_FOLLOWINGFEED = "followingfeed"
	Channel_TYPE_FOLLOWERS     = "followers"
	Channel_TYPE_CHAT          = "chat"
	// Privacy
	Channel_TYPE_PUBLIC  = "public"
	Channel_TYPE_PRIVATE = "private"
	// Koding Group Name
	Channel_KODING_NAME = "koding"
)

func NewChannel() *Channel {
	return &Channel{
		Name:         "koding",
		CreatorId:    123,
		GroupName:    Channel_KODING_NAME,
		Purpose:      "string",
		SecretKey:    "string",
		TypeConstant: Channel_TYPE_GROUP,
		Privacy:      Channel_TYPE_PRIVATE,
	}
}

func (c *Channel) BeforeCreate() {
	c.CreatedAt = time.Now()
	c.UpdatedAt = time.Now()
}

func (c *Channel) BeforeUpdate() {
	c.UpdatedAt = time.Now()
}

func (c *Channel) GetId() int64 {
	return c.Id
}

func (c *Channel) TableName() string {
	return "channel"
}

func (c *Channel) Fetch() error {
	return bongo.B.Fetch(c)
}

func (c *Channel) AfterCreate() {
	bongo.B.AfterCreate(c)
}

func (c *Channel) AfterUpdate() {
	bongo.B.AfterUpdate(c)
}

func (c *Channel) AfterDelete() {
	bongo.B.AfterDelete(c)
}

func (c *Channel) Update() error {
	if c.Name == "" || c.GroupName == "" {
		return fmt.Errorf("Validation failed %s - %s", c.Name, c.GroupName)
	}

	return bongo.B.Update(c)
}

func (c *Channel) Create() error {
	if c.Name == "" || c.GroupName == "" || c.TypeConstant == "" {
		return fmt.Errorf("Validation failed %s - %s", c.Name, c.GroupName)
	}

	// golang returns -1 if item not in the string
	if strings.Index(c.Name, " ") > -1 {
		return fmt.Errorf("Channel name %q has empty space in it", c.Name)
	}

	selector := map[string]interface{}{
		"name":       c.Name,
		"group_name": c.GroupName,
	}

	// if err is nil
	// it means we already have that channel
	err := c.One(selector)
	if err == nil {
		return fmt.Errorf("Channel %s is already created before for %s group", c.Name, c.GroupName)
	}

	if err != gorm.RecordNotFound {
		return err
	}

	return bongo.B.Create(c)
}

func (c *Channel) Delete() error {
	return bongo.B.Delete(c)
}

func (c *Channel) One(selector map[string]interface{}) error {
	return bongo.B.One(c, c, selector)
}

func (c *Channel) FetchByIds(ids []int64) ([]Channel, error) {
	var channels []Channel

	if len(ids) == 0 {
		return channels, nil
	}

	if err := bongo.B.FetchByIds(c, &channels, ids); err != nil {
		return nil, err
	}
	return channels, nil
}

func (c *Channel) AddParticipant(participantId int64) (*ChannelParticipant, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel Id is not set")
	}

	cp := NewChannelParticipant()
	cp.ChannelId = c.Id
	cp.AccountId = participantId

	err := cp.FetchParticipant()
	if err != nil && err != gorm.RecordNotFound {
		return nil, err
	}

	// if we have this record in DB
	if cp.Id != 0 {
		// if status is not active
		if cp.Status == ChannelParticipant_STATUS_ACTIVE {
			return nil, errors.New(fmt.Sprintf("Account %s is already a participant of channel %s", cp.AccountId, cp.ChannelId))
		}
		cp.Status = ChannelParticipant_STATUS_ACTIVE
		if err := cp.Update(); err != nil {
			return nil, err
		}
		return cp, nil
	}

	cp.Status = ChannelParticipant_STATUS_ACTIVE

	if err := cp.Create(); err != nil {
		return nil, err
	}

	return cp, nil
}

func (c *Channel) RemoveParticipant(participantId int64) error {
	if c.Id == 0 {
		return errors.New("Channel Id is not set")
	}

	cp := NewChannelParticipant()
	cp.ChannelId = c.Id
	cp.AccountId = participantId

	err := cp.FetchParticipant()
	// if user is not in this channel, do nothing
	if err == gorm.RecordNotFound {
		return nil
	}

	if err != nil {
		return err
	}

	if cp.Status == ChannelParticipant_STATUS_LEFT {
		return nil
	}

	cp.Status = ChannelParticipant_STATUS_LEFT
	if err := cp.Update(); err != nil {
		return err
	}

	return nil
}

func (c *Channel) FetchParticipantIds() ([]int64, error) {
	var participantIds []int64

	if c.Id == 0 {
		return participantIds, errors.New("Channel Id is not set")
	}

	query := &bongo.Query{
		Selector: map[string]interface{}{
			"channel_id": c.Id,
			"status":     ChannelParticipant_STATUS_ACTIVE,
		},
		Pluck: "account_id",
	}

	cp := NewChannelParticipant()
	err := cp.Some(&participantIds, query)
	if err != nil {
		return nil, err
	}

	return participantIds, nil
}

func (c *Channel) AddMessage(messageId int64) (*ChannelMessageList, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel Id is not set")
	}

	cml := NewChannelMessageList()
	cml.ChannelId = c.Id
	cml.MessageId = messageId

	if err := cml.Create(); err != nil {
		return nil, err
	}

	return cml, nil
}

func (c *Channel) List(q *Query) ([]Channel, error) {

	var channels []Channel

	query := &bongo.Query{
		Selector: map[string]interface{}{
			"group_name": q.GroupName,
		},
	}

	err := bongo.B.Some(c, &channels, query)
	if err != nil {
		return nil, err
	}

	return channels, nil
}
<|endoftext|>


<commit_before>package dao

import "time"

//------------------------------------------------------------
// DAO find methods
//------------------------------------------------------------

// Finds an object from collection by given equals 'and' criteria.
// Obj must be a pointer to a struct.
func (dao *DAO) FindAs(obj interface{}, equals map[string]interface{}, fields ...string) (err error) {

	err = dao.Coll.Find(equals).Select(M{}.Select(fields...)).One(obj)
	return
}

// Finds many objects matching equals 'and' criteria.
// Objs must be a pointer to an empty array of structs.
func (dao *DAO) FindManyAs(objs interface{}, equals map[string]interface{}, fields ...string) (err error) {

	err = dao.Coll.Find(equals).Select(M{}.Select(fields...)).All(objs)
	return
}

// Finds many objects matching dateKey to the specified time period.
// Note that interval is [ps, pe) meaning pe is EXCLUDED.
// Objs must be a pointer to an empty array of structs.
func (dao *DAO) FindManyByIntervalAs(objs interface{}, dateKey string, ps, pe time.Time, fields ...string) (err error) {

	q := M{dateKey: M{"$gte": ps, "$lt": pe}}
	err = dao.Coll.Find(q).Select(M{}.Select(fields...)).All(objs)
	return
}

// Finds many objects matching 'ps' and 'pe' field keys to be inside the specified time period.
// Objs must be a pointer to an empty array of structs.
func (dao *DAO) FindManyByPeriodAs(objs interface{}, psKey, peKey string, ps, pe time.Time, fields ...string) (err error) {

	q := M{psKey: M{"$gte": ps}, peKey: M{"$lte": pe}}
	err = dao.Coll.Find(q).Select(M{}.Select(fields...)).All(objs)
	return
}
<commit_msg>handle open intervals correctly: (∞, pe) and [ps, ∞)<commit_after>package dao

import "time"

//------------------------------------------------------------
// DAO find methods
//------------------------------------------------------------

// Finds an object from collection by given equals 'and' criteria.
// Obj must be a pointer to a struct.
func (dao *DAO) FindAs(obj interface{}, equals map[string]interface{}, fields ...string) (err error) {

	err = dao.Coll.Find(equals).Select(M{}.Select(fields...)).One(obj)
	return
}

// Finds many objects matching equals 'and' criteria.
// Objs must be a pointer to an empty array of structs.
func (dao *DAO) FindManyAs(objs interface{}, equals map[string]interface{}, fields ...string) (err error) {

	err = dao.Coll.Find(equals).Select(M{}.Select(fields...)).All(objs)
	return
}

// Finds many objects matching dateKey to the specified time period.
// Note that interval is [ps, pe) meaning pe is EXCLUDED.
// Objs must be a pointer to an empty array of structs.
func (dao *DAO) FindManyByIntervalAs(objs interface{}, dateKey string, ps, pe time.Time, fields ...string) (err error) {

	q := M{}
	if !ps.IsZero() || !pe.IsZero() {
		interval := M{}
		if !ps.IsZero() {
			interval["$gte"] = ps
		}
		if !pe.IsZero() {
			interval["$lt"] = pe
		}
		q[dateKey] = interval
	}

	err = dao.Coll.Find(q).Select(M{}.Select(fields...)).All(objs)
	return
}

// Finds many objects matching 'ps' and 'pe' field keys to be inside the specified time period.
// Objs must be a pointer to an empty array of structs.
func (dao *DAO) FindManyByPeriodAs(objs interface{}, psKey, peKey string, ps, pe time.Time, fields ...string) (err error) {

	q := M{psKey: M{"$gte": ps}, peKey: M{"$lte": pe}}
	err = dao.Coll.Find(q).Select(M{}.Select(fields...)).All(objs)
	return
}
<|endoftext|>


<commit_before>package websocket

import (
	"github.com/smancke/guble/protocol"
	"github.com/smancke/guble/server/auth"
	"github.com/smancke/guble/server/router"

	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/websocket"
	"github.com/rs/xid"

	"fmt"
	"net/http"
	"strings"
	"time"
)

var webSocketUpgrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool { return true },
}

type WSHandler struct {
	Router        router.Router
	prefix        string
	accessManager auth.AccessManager
}

func NewWSHandler(router router.Router, prefix string) (*WSHandler, error) {
	accessManager, err := router.AccessManager()
	if err != nil {
		return nil, err
	}

	return &WSHandler{
		Router:        router,
		prefix:        prefix,
		accessManager: accessManager,
	}, nil
}

func (handle *WSHandler) GetPrefix() string {
	return handle.prefix
}

func (handle *WSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	c, err := webSocketUpgrader.Upgrade(w, r, nil)
	if err != nil {
		logger.WithError(err).Error("Error on upgrading to websocket")
		return
	}
	defer c.Close()

	NewWebSocket(handle, &wsconn{c}, extractUserID(r.RequestURI)).Start()
}

// WSConnection is a wrapper interface for the needed functions of the websocket.Conn
// It is introduced for testability of the WSHandler
type WSConnection interface {
	Close()
	Send(bytes []byte) (err error)
	Receive(bytes *[]byte) (err error)
}

// wsconnImpl is a Wrapper of the websocket.Conn
// implementing the interface WSConn for better testability
type wsconn struct {
	*websocket.Conn
}

func (conn *wsconn) Close() {
	conn.Conn.Close()
}

func (conn *wsconn) Send(bytes []byte) error {
	return conn.WriteMessage(websocket.BinaryMessage, bytes)
}

func (conn *wsconn) Receive(bytes *[]byte) (err error) {
	_, *bytes, err = conn.ReadMessage()
	return err
}

// WebSocket struct represents a websocket
type WebSocket struct {
	*WSHandler
	WSConnection
	applicationID string
	userID        string
	sendChannel   chan []byte
	receivers     map[protocol.Path]*Receiver
}

func NewWebSocket(handler *WSHandler, wsConn WSConnection, userID string) *WebSocket {
	return &WebSocket{
		WSHandler:     handler,
		WSConnection:  wsConn,
		applicationID: xid.New().String(),
		userID:        userID,
		sendChannel:   make(chan []byte, 10),
		receivers:     make(map[protocol.Path]*Receiver),
	}
}

func (ws *WebSocket) Start() error {
	ws.sendConnectionMessage()
	go ws.sendLoop()
	ws.receiveLoop()
	return nil
}

func (ws *WebSocket) sendLoop() {
	for {
		select {
		case raw := <-ws.sendChannel:

			logger.WithFields(log.Fields{
				"userId":        ws.userID,
				"applicationID": ws.applicationID,
				"totalSize":     len(raw),
				"actualContent": string(raw),
			}).Debug("Received on websocket")

			if ws.checkAccess(raw) {

				if err := ws.Send(raw); err != nil {

					logger.WithFields(log.Fields{
						"applicationID": ws.applicationID,
					}).Debug("Closed connnection with")

					ws.cleanAndClose()
					break
				}
			}
		}
	}
}

func (ws *WebSocket) checkAccess(raw []byte) bool {
	if raw[0] == byte('/') {
		path := getPathFromRawMessage(raw)

		logger.WithFields(log.Fields{
			"userID": ws.userID,
			"path":   path,
		}).Debug("Received msg")

		return len(path) == 0 || ws.accessManager.IsAllowed(auth.READ, ws.userID, path)

	}
	return true
}

func getPathFromRawMessage(raw []byte) protocol.Path {
	i := strings.Index(string(raw), ",")
	return protocol.Path(raw[:i])
}

func (ws *WebSocket) receiveLoop() {
	var message []byte
	for {
		err := ws.Receive(&message)
		if err != nil {

			logger.WithFields(log.Fields{
				"applicationID": ws.applicationID,
			}).Debug("Closed connnection by application")

			ws.cleanAndClose()
			break
		}

		//protocol.Debug("websocket_connector, raw message received: %v", string(message))
		cmd, err := protocol.ParseCmd(message)
		if err != nil {
			ws.sendError(protocol.ERROR_BAD_REQUEST, "error parsing command. %v", err.Error())
			continue
		}
		switch cmd.Name {
		case protocol.CmdSend:
			ws.handleSendCmd(cmd)
		case protocol.CmdReceive:
			ws.handleReceiveCmd(cmd)
		case protocol.CmdCancel:
			ws.handleCancelCmd(cmd)
		default:
			ws.sendError(protocol.ERROR_BAD_REQUEST, "unknown command %v", cmd.Name)
		}
	}
}

func (ws *WebSocket) sendConnectionMessage() {
	n := &protocol.NotificationMessage{
		Name: protocol.SUCCESS_CONNECTED,
		Arg:  "You are connected to the server.",
		Json: fmt.Sprintf(`{"ApplicationId": "%s", "UserId": "%s", "Time": "%s"}`, ws.applicationID, ws.userID, time.Now().Format(time.RFC3339)),
	}
	ws.sendChannel <- n.Bytes()
}

func (ws *WebSocket) handleReceiveCmd(cmd *protocol.Cmd) {
	rec, err := NewReceiverFromCmd(
		ws.applicationID,
		cmd,
		ws.sendChannel,
		ws.Router,
		ws.userID,
	)
	if err != nil {
		logger.WithError(err).Error("Client error in handleReceiveCmd")
		ws.sendError(protocol.ERROR_BAD_REQUEST, err.Error())
		return
	}
	ws.receivers[rec.path] = rec
	rec.Start()
}

func (ws *WebSocket) handleCancelCmd(cmd *protocol.Cmd) {
	if len(cmd.Arg) == 0 {
		ws.sendError(protocol.ERROR_BAD_REQUEST, "- command requires a path argument, but none given")
		return
	}
	path := protocol.Path(cmd.Arg)
	rec, exist := ws.receivers[path]
	if exist {
		rec.Stop()
		delete(ws.receivers, path)
	}
}

func (ws *WebSocket) handleSendCmd(cmd *protocol.Cmd) {
	logger.WithFields(log.Fields{
		"cmd": string(cmd.Bytes()),
	}).Debug("Sending ")

	if len(cmd.Arg) == 0 {
		ws.sendError(protocol.ERROR_BAD_REQUEST, "send command requires a path argument, but none given")
		return
	}

	args := strings.SplitN(cmd.Arg, " ", 2)
	msg := &protocol.Message{
		Path:          protocol.Path(args[0]),
		ApplicationID: ws.applicationID,
		UserID:        ws.userID,
		HeaderJSON:    cmd.HeaderJSON,
		Body:          cmd.Body,
	}
	if len(args) == 2 {
		msg.OptionalID = args[1]
	}

	ws.Router.HandleMessage(msg)

	ws.sendOK(protocol.SUCCESS_SEND, msg.OptionalID)
}

func (ws *WebSocket) cleanAndClose() {

	logger.WithFields(log.Fields{
		"applicationID": ws.applicationID,
	}).Debug("Closing applicationId")

	for path, rec := range ws.receivers {
		rec.Stop()
		delete(ws.receivers, path)
	}

	ws.Close()
}

func (ws *WebSocket) sendError(name string, argPattern string, params ...interface{}) {
	n := &protocol.NotificationMessage{
		Name:    name,
		Arg:     fmt.Sprintf(argPattern, params...),
		IsError: true,
	}
	ws.sendChannel <- n.Bytes()
}

func (ws *WebSocket) sendOK(name string, argPattern string, params ...interface{}) {
	n := &protocol.NotificationMessage{
		Name:    name,
		Arg:     fmt.Sprintf(argPattern, params...),
		IsError: false,
	}
	ws.sendChannel <- n.Bytes()
}

// Extracts the userID out of an URI or empty string if format not met
// Example:
// 		http://example.com/user/user01/ -> user01
// 		http://example.com/user/ -> ""
func extractUserID(uri string) string {
	uriParts := strings.SplitN(uri, "/user/", 2)
	if len(uriParts) != 2 {
		return ""
	}
	return uriParts[1]
}
<commit_msg>simplified sendLoop func<commit_after>package websocket

import (
	"github.com/smancke/guble/protocol"
	"github.com/smancke/guble/server/auth"
	"github.com/smancke/guble/server/router"

	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/websocket"
	"github.com/rs/xid"

	"fmt"
	"net/http"
	"strings"
	"time"
)

var webSocketUpgrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool { return true },
}

type WSHandler struct {
	Router        router.Router
	prefix        string
	accessManager auth.AccessManager
}

func NewWSHandler(router router.Router, prefix string) (*WSHandler, error) {
	accessManager, err := router.AccessManager()
	if err != nil {
		return nil, err
	}

	return &WSHandler{
		Router:        router,
		prefix:        prefix,
		accessManager: accessManager,
	}, nil
}

func (handle *WSHandler) GetPrefix() string {
	return handle.prefix
}

func (handle *WSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	c, err := webSocketUpgrader.Upgrade(w, r, nil)
	if err != nil {
		logger.WithError(err).Error("Error on upgrading to websocket")
		return
	}
	defer c.Close()

	NewWebSocket(handle, &wsconn{c}, extractUserID(r.RequestURI)).Start()
}

// WSConnection is a wrapper interface for the needed functions of the websocket.Conn
// It is introduced for testability of the WSHandler
type WSConnection interface {
	Close()
	Send(bytes []byte) (err error)
	Receive(bytes *[]byte) (err error)
}

// wsconnImpl is a Wrapper of the websocket.Conn
// implementing the interface WSConn for better testability
type wsconn struct {
	*websocket.Conn
}

func (conn *wsconn) Close() {
	conn.Conn.Close()
}

func (conn *wsconn) Send(bytes []byte) error {
	return conn.WriteMessage(websocket.BinaryMessage, bytes)
}

func (conn *wsconn) Receive(bytes *[]byte) (err error) {
	_, *bytes, err = conn.ReadMessage()
	return err
}

// WebSocket struct represents a websocket
type WebSocket struct {
	*WSHandler
	WSConnection
	applicationID string
	userID        string
	sendChannel   chan []byte
	receivers     map[protocol.Path]*Receiver
}

func NewWebSocket(handler *WSHandler, wsConn WSConnection, userID string) *WebSocket {
	return &WebSocket{
		WSHandler:     handler,
		WSConnection:  wsConn,
		applicationID: xid.New().String(),
		userID:        userID,
		sendChannel:   make(chan []byte, 10),
		receivers:     make(map[protocol.Path]*Receiver),
	}
}

func (ws *WebSocket) Start() error {
	ws.sendConnectionMessage()
	go ws.sendLoop()
	ws.receiveLoop()
	return nil
}

func (ws *WebSocket) sendLoop() {
	for raw := range ws.sendChannel {
		if !ws.checkAccess(raw) {
			continue
		}
		if err := ws.Send(raw); err != nil {
			logger.WithFields(log.Fields{
				"userId":        ws.userID,
				"applicationID": ws.applicationID,
				"totalSize":     len(raw),
				"actualContent": string(raw),
			}).Error("Could not send")
			ws.cleanAndClose()
			break
		}
	}
}

func (ws *WebSocket) checkAccess(raw []byte) bool {
	if raw[0] == byte('/') {
		path := getPathFromRawMessage(raw)

		logger.WithFields(log.Fields{
			"userID": ws.userID,
			"path":   path,
		}).Debug("Received msg")

		return len(path) == 0 || ws.accessManager.IsAllowed(auth.READ, ws.userID, path)

	}
	return true
}

func getPathFromRawMessage(raw []byte) protocol.Path {
	i := strings.Index(string(raw), ",")
	return protocol.Path(raw[:i])
}

func (ws *WebSocket) receiveLoop() {
	var message []byte
	for {
		err := ws.Receive(&message)
		if err != nil {

			logger.WithFields(log.Fields{
				"applicationID": ws.applicationID,
			}).Debug("Closed connnection by application")

			ws.cleanAndClose()
			break
		}

		//protocol.Debug("websocket_connector, raw message received: %v", string(message))
		cmd, err := protocol.ParseCmd(message)
		if err != nil {
			ws.sendError(protocol.ERROR_BAD_REQUEST, "error parsing command. %v", err.Error())
			continue
		}
		switch cmd.Name {
		case protocol.CmdSend:
			ws.handleSendCmd(cmd)
		case protocol.CmdReceive:
			ws.handleReceiveCmd(cmd)
		case protocol.CmdCancel:
			ws.handleCancelCmd(cmd)
		default:
			ws.sendError(protocol.ERROR_BAD_REQUEST, "unknown command %v", cmd.Name)
		}
	}
}

func (ws *WebSocket) sendConnectionMessage() {
	n := &protocol.NotificationMessage{
		Name: protocol.SUCCESS_CONNECTED,
		Arg:  "You are connected to the server.",
		Json: fmt.Sprintf(`{"ApplicationId": "%s", "UserId": "%s", "Time": "%s"}`, ws.applicationID, ws.userID, time.Now().Format(time.RFC3339)),
	}
	ws.sendChannel <- n.Bytes()
}

func (ws *WebSocket) handleReceiveCmd(cmd *protocol.Cmd) {
	rec, err := NewReceiverFromCmd(
		ws.applicationID,
		cmd,
		ws.sendChannel,
		ws.Router,
		ws.userID,
	)
	if err != nil {
		logger.WithError(err).Error("Client error in handleReceiveCmd")
		ws.sendError(protocol.ERROR_BAD_REQUEST, err.Error())
		return
	}
	ws.receivers[rec.path] = rec
	rec.Start()
}

func (ws *WebSocket) handleCancelCmd(cmd *protocol.Cmd) {
	if len(cmd.Arg) == 0 {
		ws.sendError(protocol.ERROR_BAD_REQUEST, "- command requires a path argument, but none given")
		return
	}
	path := protocol.Path(cmd.Arg)
	rec, exist := ws.receivers[path]
	if exist {
		rec.Stop()
		delete(ws.receivers, path)
	}
}

func (ws *WebSocket) handleSendCmd(cmd *protocol.Cmd) {
	logger.WithFields(log.Fields{
		"cmd": string(cmd.Bytes()),
	}).Debug("Sending ")

	if len(cmd.Arg) == 0 {
		ws.sendError(protocol.ERROR_BAD_REQUEST, "send command requires a path argument, but none given")
		return
	}

	args := strings.SplitN(cmd.Arg, " ", 2)
	msg := &protocol.Message{
		Path:          protocol.Path(args[0]),
		ApplicationID: ws.applicationID,
		UserID:        ws.userID,
		HeaderJSON:    cmd.HeaderJSON,
		Body:          cmd.Body,
	}
	if len(args) == 2 {
		msg.OptionalID = args[1]
	}

	ws.Router.HandleMessage(msg)

	ws.sendOK(protocol.SUCCESS_SEND, msg.OptionalID)
}

func (ws *WebSocket) cleanAndClose() {

	logger.WithFields(log.Fields{
		"applicationID": ws.applicationID,
	}).Debug("Closing applicationId")

	for path, rec := range ws.receivers {
		rec.Stop()
		delete(ws.receivers, path)
	}

	ws.Close()
}

func (ws *WebSocket) sendError(name string, argPattern string, params ...interface{}) {
	n := &protocol.NotificationMessage{
		Name:    name,
		Arg:     fmt.Sprintf(argPattern, params...),
		IsError: true,
	}
	ws.sendChannel <- n.Bytes()
}

func (ws *WebSocket) sendOK(name string, argPattern string, params ...interface{}) {
	n := &protocol.NotificationMessage{
		Name:    name,
		Arg:     fmt.Sprintf(argPattern, params...),
		IsError: false,
	}
	ws.sendChannel <- n.Bytes()
}

// Extracts the userID out of an URI or empty string if format not met
// Example:
// 		http://example.com/user/user01/ -> user01
// 		http://example.com/user/ -> ""
func extractUserID(uri string) string {
	uriParts := strings.SplitN(uri, "/user/", 2)
	if len(uriParts) != 2 {
		return ""
	}
	return uriParts[1]
}
<|endoftext|>