<commit_before>package postgres

import (
	"strings"

	"github.com/lfq7413/tomato/errs"
	"github.com/lfq7413/tomato/types"
	"github.com/lfq7413/tomato/utils"
)

const postgresSchemaCollectionName = "_SCHEMA"

const postgresRelationDoesNotExistError = "42P01"
const postgresDuplicateRelationError = "42P07"
const postgresDuplicateColumnError = "42701"
const postgresUniqueIndexViolationError = "23505"
const postgresTransactionAbortedError = "25P02"

// PostgresAdapter postgres 数据库适配器
type PostgresAdapter struct {
	collectionPrefix string
	collectionList   []string
}

// NewPostgresAdapter ...
func NewPostgresAdapter(collectionPrefix string) *PostgresAdapter {
	return &PostgresAdapter{
		collectionPrefix: collectionPrefix,
		collectionList:   []string{},
	}
}

// ClassExists ...
func (p *PostgresAdapter) ClassExists(name string) bool {
	return false
}

// SetClassLevelPermissions ...
func (p *PostgresAdapter) SetClassLevelPermissions(className string, CLPs types.M) error {
	return nil
}

// CreateClass ...
func (p *PostgresAdapter) CreateClass(className string, schema types.M) (types.M, error) {
	return nil, nil
}

// AddFieldIfNotExists ...
func (p *PostgresAdapter) AddFieldIfNotExists(className, fieldName string, fieldType types.M) error {
	return nil
}

// DeleteClass ...
func (p *PostgresAdapter) DeleteClass(className string) (types.M, error) {
	return nil, nil
}

// DeleteAllClasses ...
func (p *PostgresAdapter) DeleteAllClasses() error {
	return nil
}

// DeleteFields ...
func (p *PostgresAdapter) DeleteFields(className string, schema types.M, fieldNames []string) error {
	return nil
}

// CreateObject ...
func (p *PostgresAdapter) CreateObject(className string, schema, object types.M) error {
	return nil
}

// GetAllClasses ...
func (p *PostgresAdapter) GetAllClasses() ([]types.M, error) {
	return nil, nil
}

// GetClass ...
func (p *PostgresAdapter) GetClass(className string) (types.M, error) {
	return nil, nil
}

// DeleteObjectsByQuery ...
func (p *PostgresAdapter) DeleteObjectsByQuery(className string, schema, query types.M) error {
	return nil
}

// Find ...
func (p *PostgresAdapter) Find(className string, schema, query, options types.M) ([]types.M, error) {
	return nil, nil
}

// Count ...
func (p *PostgresAdapter) Count(className string, schema, query types.M) (int, error) {
	return 0, nil
}

// UpdateObjectsByQuery ...
func (p *PostgresAdapter) UpdateObjectsByQuery(className string, schema, query, update types.M) error {
	return nil
}

// FindOneAndUpdate ...
func (p *PostgresAdapter) FindOneAndUpdate(className string, schema, query, update types.M) (types.M, error) {
	return nil, nil
}

// UpsertOneObject ...
func (p *PostgresAdapter) UpsertOneObject(className string, schema, query, update types.M) error {
	return nil
}

// EnsureUniqueness ...
func (p *PostgresAdapter) EnsureUniqueness(className string, schema types.M, fieldNames []string) error {
	return nil
}

// PerformInitialization ...
func (p *PostgresAdapter) PerformInitialization(options types.M) error {
	return nil
}

var parseToPosgresComparator = map[string]string{
	"$gt":  ">",
	"$lt":  "<",
	"$gte": ">=",
	"$lte": "<=",
}

func parseTypeToPostgresType(t types.M) (string, error) {
	if t == nil {
		return "", nil
	}
	tp := utils.S(t["type"])
	switch tp {
	case "String":
		return "text", nil
	case "Date":
		return "timestamp with time zone", nil
	case "Object":
		return "jsonb", nil
	case "File":
		return "text", nil
	case "Boolean":
		return "boolean", nil
	case "Pointer":
		return "char(10)", nil
	case "Number":
		return "double precision", nil
	case "GeoPoint":
		return "point", nil
	case "Array":
		if contents := utils.M(t["contents"]); contents != nil {
			if utils.S(contents["type"]) == "String" {
				return "text[]", nil
			}
		}
		return "jsonb", nil
	default:
		return "", errs.E(errs.IncorrectType, "no type for "+tp+" yet")
	}
}

func toPostgresValue(value interface{}) interface{} {
	if v := utils.M(value); v != nil {
		if utils.S(v["__type"]) == "Date" {
			return v["iso"]
		}
		if utils.S(v["__type"]) == "File" {
			return v["name"]
		}
	}
	return value
}

func transformValue(value interface{}) interface{} {
	if v := utils.M(value); v != nil {
		if utils.S(v["__type"]) == "Pointer" {
			return v["objectId"]
		}
	}
	return value
}

var emptyCLPS = types.M{
	"find":     types.M{},
	"get":      types.M{},
	"create":   types.M{},
	"update":   types.M{},
	"delete":   types.M{},
	"addField": types.M{},
}

var defaultCLPS = types.M{
	"find":     types.M{"*": true},
	"get":      types.M{"*": true},
	"create":   types.M{"*": true},
	"update":   types.M{"*": true},
	"delete":   types.M{"*": true},
	"addField": types.M{"*": true},
}

func toParseSchema(schema types.M) types.M {
	if schema == nil {
		return nil
	}

	var fields types.M
	if fields = utils.M(schema["fields"]); fields == nil {
		fields = types.M{}
	}

	if utils.S(schema["className"]) == "_User" {
		if _, ok := fields["_hashed_password"]; ok {
			delete(fields, "_hashed_password")
		}
	}

	if _, ok := fields["_wperm"]; ok {
		delete(fields, "_wperm")
	}
	if _, ok := fields["_rperm"]; ok {
		delete(fields, "_rperm")
	}

	var clps types.M
	clps = utils.CopyMap(defaultCLPS)
	if classLevelPermissions := utils.M(schema["classLevelPermissions"]); classLevelPermissions != nil {
		// clps = utils.CopyMap(emptyCLPS)
		// 不存在的 action 默认为公共权限
		for k, v := range classLevelPermissions {
			clps[k] = v
		}
	}

	return types.M{
		"className":             schema["className"],
		"fields":                fields,
		"classLevelPermissions": clps,
	}
}

func toPostgresSchema(schema types.M) types.M {
	if schema == nil {
		return nil
	}

	var fields types.M
	if fields = utils.M(schema["fields"]); fields == nil {
		fields = types.M{}
	}

	fields["_wperm"] = types.M{
		"type":     "Array",
		"contents": types.M{"type": "String"},
	}
	fields["_rperm"] = types.M{
		"type":     "Array",
		"contents": types.M{"type": "String"},
	}

	if utils.S(schema["className"]) == "_User" {
		fields["_hashed_password"] = types.M{"type": "String"}
		fields["_password_history"] = types.M{"type": "Array"}
	}

	schema["fields"] = fields

	return schema
}

func handleDotFields(object types.M) types.M {
	for fieldName := range object {
		if strings.Index(fieldName, ".") == -1 {
			continue
		}
		components := strings.Split(fieldName, ".")

		value := object[fieldName]
		if v := utils.M(value); v != nil {
			if utils.S(v["__op"]) == "Delete" {
				value = nil
			}
		}

		currentObj := object
		for i, next := range components {
			if i == (len(components) - 1) {
				currentObj[next] = value
				break
			}
			obj := currentObj[next]
			if obj == nil {
				obj = types.M{}
				currentObj[next] = obj
			}
			currentObj = utils.M(currentObj[next])
		}

		delete(object, fieldName)
	}
	return object
}

func validateKeys(object interface{}) error {
	if obj := utils.M(object); obj != nil {
		for key, value := range obj {
			err := validateKeys(value)
			if err != nil {
				return err
			}

			if strings.Contains(key, "$") || strings.Contains(key, ".") {
				return errs.E(errs.InvalidNestedKey, "Nested keys should not contain the '$' or '.' characters")
			}
		}
	}
	return nil
}

func joinTablesForSchema(schema types.M) []string {
	list := []string{}
	if schema != nil {
		if fields := utils.M(schema["fields"]); fields != nil {
			className := utils.S(schema["className"])
			for field, v := range fields {
				if tp := utils.M(v); tp != nil {
					if utils.S(tp["type"]) == "Relation" {
						list = append(list, "_Join:"+field+":"+className)
					}
				}
			}
		}
	}
	return list
}

func buildWhereClause(schema, query types.M, index int) (types.M, error) {
	// TODO
	// removeWhiteSpace
	// processRegexPattern
	return nil, nil
}

func removeWhiteSpace(s string) string {
	// TODO
	return ""
}

func processRegexPattern(s string) string {
	// TODO
	// literalizeRegexPart
	return ""
}

func createLiteralRegex(s string) string {
	// TODO
	return ""
}

func literalizeRegexPart(s string) string {
	// TODO
	// createLiteralRegex
	return ""
}
<commit_msg>完成 createLiteralRegex<commit_after>package postgres

import (
	"strings"

	"regexp"

	"github.com/lfq7413/tomato/errs"
	"github.com/lfq7413/tomato/types"
	"github.com/lfq7413/tomato/utils"
)

const postgresSchemaCollectionName = "_SCHEMA"

const postgresRelationDoesNotExistError = "42P01"
const postgresDuplicateRelationError = "42P07"
const postgresDuplicateColumnError = "42701"
const postgresUniqueIndexViolationError = "23505"
const postgresTransactionAbortedError = "25P02"

// PostgresAdapter postgres 数据库适配器
type PostgresAdapter struct {
	collectionPrefix string
	collectionList   []string
}

// NewPostgresAdapter ...
func NewPostgresAdapter(collectionPrefix string) *PostgresAdapter {
	return &PostgresAdapter{
		collectionPrefix: collectionPrefix,
		collectionList:   []string{},
	}
}

// ClassExists ...
func (p *PostgresAdapter) ClassExists(name string) bool {
	return false
}

// SetClassLevelPermissions ...
func (p *PostgresAdapter) SetClassLevelPermissions(className string, CLPs types.M) error {
	return nil
}

// CreateClass ...
func (p *PostgresAdapter) CreateClass(className string, schema types.M) (types.M, error) {
	return nil, nil
}

// AddFieldIfNotExists ...
func (p *PostgresAdapter) AddFieldIfNotExists(className, fieldName string, fieldType types.M) error {
	return nil
}

// DeleteClass ...
func (p *PostgresAdapter) DeleteClass(className string) (types.M, error) {
	return nil, nil
}

// DeleteAllClasses ...
func (p *PostgresAdapter) DeleteAllClasses() error {
	return nil
}

// DeleteFields ...
func (p *PostgresAdapter) DeleteFields(className string, schema types.M, fieldNames []string) error {
	return nil
}

// CreateObject ...
func (p *PostgresAdapter) CreateObject(className string, schema, object types.M) error {
	return nil
}

// GetAllClasses ...
func (p *PostgresAdapter) GetAllClasses() ([]types.M, error) {
	return nil, nil
}

// GetClass ...
func (p *PostgresAdapter) GetClass(className string) (types.M, error) {
	return nil, nil
}

// DeleteObjectsByQuery ...
func (p *PostgresAdapter) DeleteObjectsByQuery(className string, schema, query types.M) error {
	return nil
}

// Find ...
func (p *PostgresAdapter) Find(className string, schema, query, options types.M) ([]types.M, error) {
	return nil, nil
}

// Count ...
func (p *PostgresAdapter) Count(className string, schema, query types.M) (int, error) {
	return 0, nil
}

// UpdateObjectsByQuery ...
func (p *PostgresAdapter) UpdateObjectsByQuery(className string, schema, query, update types.M) error {
	return nil
}

// FindOneAndUpdate ...
func (p *PostgresAdapter) FindOneAndUpdate(className string, schema, query, update types.M) (types.M, error) {
	return nil, nil
}

// UpsertOneObject ...
func (p *PostgresAdapter) UpsertOneObject(className string, schema, query, update types.M) error {
	return nil
}

// EnsureUniqueness ...
func (p *PostgresAdapter) EnsureUniqueness(className string, schema types.M, fieldNames []string) error {
	return nil
}

// PerformInitialization ...
func (p *PostgresAdapter) PerformInitialization(options types.M) error {
	return nil
}

var parseToPosgresComparator = map[string]string{
	"$gt":  ">",
	"$lt":  "<",
	"$gte": ">=",
	"$lte": "<=",
}

func parseTypeToPostgresType(t types.M) (string, error) {
	if t == nil {
		return "", nil
	}
	tp := utils.S(t["type"])
	switch tp {
	case "String":
		return "text", nil
	case "Date":
		return "timestamp with time zone", nil
	case "Object":
		return "jsonb", nil
	case "File":
		return "text", nil
	case "Boolean":
		return "boolean", nil
	case "Pointer":
		return "char(10)", nil
	case "Number":
		return "double precision", nil
	case "GeoPoint":
		return "point", nil
	case "Array":
		if contents := utils.M(t["contents"]); contents != nil {
			if utils.S(contents["type"]) == "String" {
				return "text[]", nil
			}
		}
		return "jsonb", nil
	default:
		return "", errs.E(errs.IncorrectType, "no type for "+tp+" yet")
	}
}

func toPostgresValue(value interface{}) interface{} {
	if v := utils.M(value); v != nil {
		if utils.S(v["__type"]) == "Date" {
			return v["iso"]
		}
		if utils.S(v["__type"]) == "File" {
			return v["name"]
		}
	}
	return value
}

func transformValue(value interface{}) interface{} {
	if v := utils.M(value); v != nil {
		if utils.S(v["__type"]) == "Pointer" {
			return v["objectId"]
		}
	}
	return value
}

var emptyCLPS = types.M{
	"find":     types.M{},
	"get":      types.M{},
	"create":   types.M{},
	"update":   types.M{},
	"delete":   types.M{},
	"addField": types.M{},
}

var defaultCLPS = types.M{
	"find":     types.M{"*": true},
	"get":      types.M{"*": true},
	"create":   types.M{"*": true},
	"update":   types.M{"*": true},
	"delete":   types.M{"*": true},
	"addField": types.M{"*": true},
}

func toParseSchema(schema types.M) types.M {
	if schema == nil {
		return nil
	}

	var fields types.M
	if fields = utils.M(schema["fields"]); fields == nil {
		fields = types.M{}
	}

	if utils.S(schema["className"]) == "_User" {
		if _, ok := fields["_hashed_password"]; ok {
			delete(fields, "_hashed_password")
		}
	}

	if _, ok := fields["_wperm"]; ok {
		delete(fields, "_wperm")
	}
	if _, ok := fields["_rperm"]; ok {
		delete(fields, "_rperm")
	}

	var clps types.M
	clps = utils.CopyMap(defaultCLPS)
	if classLevelPermissions := utils.M(schema["classLevelPermissions"]); classLevelPermissions != nil {
		// clps = utils.CopyMap(emptyCLPS)
		// 不存在的 action 默认为公共权限
		for k, v := range classLevelPermissions {
			clps[k] = v
		}
	}

	return types.M{
		"className":             schema["className"],
		"fields":                fields,
		"classLevelPermissions": clps,
	}
}

func toPostgresSchema(schema types.M) types.M {
	if schema == nil {
		return nil
	}

	var fields types.M
	if fields = utils.M(schema["fields"]); fields == nil {
		fields = types.M{}
	}

	fields["_wperm"] = types.M{
		"type":     "Array",
		"contents": types.M{"type": "String"},
	}
	fields["_rperm"] = types.M{
		"type":     "Array",
		"contents": types.M{"type": "String"},
	}

	if utils.S(schema["className"]) == "_User" {
		fields["_hashed_password"] = types.M{"type": "String"}
		fields["_password_history"] = types.M{"type": "Array"}
	}

	schema["fields"] = fields

	return schema
}

func handleDotFields(object types.M) types.M {
	for fieldName := range object {
		if strings.Index(fieldName, ".") == -1 {
			continue
		}
		components := strings.Split(fieldName, ".")

		value := object[fieldName]
		if v := utils.M(value); v != nil {
			if utils.S(v["__op"]) == "Delete" {
				value = nil
			}
		}

		currentObj := object
		for i, next := range components {
			if i == (len(components) - 1) {
				currentObj[next] = value
				break
			}
			obj := currentObj[next]
			if obj == nil {
				obj = types.M{}
				currentObj[next] = obj
			}
			currentObj = utils.M(currentObj[next])
		}

		delete(object, fieldName)
	}
	return object
}

func validateKeys(object interface{}) error {
	if obj := utils.M(object); obj != nil {
		for key, value := range obj {
			err := validateKeys(value)
			if err != nil {
				return err
			}

			if strings.Contains(key, "$") || strings.Contains(key, ".") {
				return errs.E(errs.InvalidNestedKey, "Nested keys should not contain the '$' or '.' characters")
			}
		}
	}
	return nil
}

func joinTablesForSchema(schema types.M) []string {
	list := []string{}
	if schema != nil {
		if fields := utils.M(schema["fields"]); fields != nil {
			className := utils.S(schema["className"])
			for field, v := range fields {
				if tp := utils.M(v); tp != nil {
					if utils.S(tp["type"]) == "Relation" {
						list = append(list, "_Join:"+field+":"+className)
					}
				}
			}
		}
	}
	return list
}

func buildWhereClause(schema, query types.M, index int) (types.M, error) {
	// TODO
	// removeWhiteSpace
	// processRegexPattern
	return nil, nil
}

func removeWhiteSpace(s string) string {
	// TODO
	return ""
}

func processRegexPattern(s string) string {
	// TODO
	// literalizeRegexPart
	return ""
}

func createLiteralRegex(s string) string {
	chars := strings.Split(s, "")
	for i, c := range chars {
		if m, _ := regexp.MatchString(`[0-9a-zA-Z]`, c); m == false {
			if c == `'` {
				chars[i] = `''`
			} else {
				chars[i] = `\\` + c
			}
		}
	}
	return strings.Join(chars, "")
}

func literalizeRegexPart(s string) string {
	// TODO
	return ""
}
<|endoftext|>


<commit_before>package store

import (
	"github.com/smancke/guble/protocol"

	"github.com/stretchr/testify/assert"

	"io/ioutil"
	"math"
	"os"
	"strconv"
	"testing"
	"time"
)

func Test_MessagePartition_foConcurrentWriteAndReads(t *testing.T) {
	//defer enableDebugForMethod()()
	a := assert.New(t)
	dir, _ := ioutil.TempDir("", "partition_store_test")
	defer os.RemoveAll(dir)

	store, _ := NewMessagePartition(dir, "myMessages")

	n := 1000 * 100

	writerDone := make(chan bool)
	readerDone := make(chan bool)
	go messagePartitionWriter(a, store, n, writerDone)
	go messagePartitionReader("reader1", a, store, n, readerDone)
	go messagePartitionReader("reader2", a, store, n, readerDone)
	go messagePartitionReader("reader3", a, store, n, readerDone)
	go messagePartitionReader("reader4", a, store, n, readerDone)
	go messagePartitionReader("reader5", a, store, n, readerDone)

	select {
	case <-writerDone:
	case <-time.After(time.Second * 15):
		a.Fail("writer timed out")
	}

	timeout := time.After(time.Second * 15)
	for i := 0; i < 5; i++ {
		select {
		case <-readerDone:
		case <-timeout:
			a.Fail("reader timed out")
		}
	}
}

func messagePartitionWriter(a *assert.Assertions, store *MessagePartition, n int, done chan bool) {
	for i := 1; i <= n; i++ {
		msg := []byte("Hello " + strconv.Itoa(i))
		a.NoError(store.Store(uint64(i), msg))
	}
	done <- true
}

func messagePartitionReader(name string, a *assert.Assertions, store *MessagePartition, n int, done chan bool) {
	lastReadMessage := 0
	for lastReadMessage < n {

		msgC := make(chan MessageAndId)
		errorC := make(chan error)

		protocol.Debug("[%v] start fetching at: %v", name, lastReadMessage+1)
		store.Fetch(FetchRequest{
			Partition:     "myMessages",
			StartId:       uint64(lastReadMessage + 1),
			Direction:     1,
			Count:         math.MaxInt32,
			MessageC:      msgC,
			ErrorCallback: errorC,
			StartCallback: make(chan int, 1),
		})

	fetch:

		for {
			select {
			case msgAndId, open := <-msgC:
				if !open {
					protocol.Debug("[%v] stop fetching at %v", name, lastReadMessage)
					break fetch
				}
				a.Equal(lastReadMessage+1, int(msgAndId.Id))
				lastReadMessage = int(msgAndId.Id)
			case err := <-errorC:
				a.Fail("received error", err.Error())
				<-done
				return
			}
		}

	}
	protocol.Debug("[%v] ready, got %v", name, lastReadMessage)
	done <- true
}
<commit_msg>refactoring test<commit_after>package store

import (
	"github.com/smancke/guble/protocol"

	"github.com/stretchr/testify/assert"

	"io/ioutil"
	"math"
	"os"
	"strconv"
	"testing"
	"time"
)

func Test_MessagePartition_forConcurrentWriteAndReads(t *testing.T) {
	//defer enableDebugForMethod()()
	a := assert.New(t)
	dir, _ := ioutil.TempDir("", "partition_store_test")
	defer os.RemoveAll(dir)

	store, _ := NewMessagePartition(dir, "myMessages")

	n := 1000 * 100
	nReaders := 5

	writerDone := make(chan bool)
	go messagePartitionWriter(a, store, n, writerDone)

	readerDone := make(chan bool)
	for i := 0; i < nReaders; i++ {
		go messagePartitionReader("reader" + strconv.Itoa(i), a, store, n, readerDone)
	}

	select {
	case <-writerDone:
	case <-time.After(time.Second * 15):
		a.Fail("writer timed out")
	}

	timeout := time.After(time.Second * 15)
	for i := 0; i < nReaders; i++ {
		select {
		case <-readerDone:
		case <-timeout:
			a.Fail("reader timed out")
		}
	}
}

func messagePartitionWriter(a *assert.Assertions, store *MessagePartition, n int, done chan bool) {
	for i := 1; i <= n; i++ {
		msg := []byte("Hello " + strconv.Itoa(i))
		a.NoError(store.Store(uint64(i), msg))
	}
	done <- true
}

func messagePartitionReader(name string, a *assert.Assertions, store *MessagePartition, n int, done chan bool) {
	lastReadMessage := 0
	for lastReadMessage < n {

		msgC := make(chan MessageAndId)
		errorC := make(chan error)

		protocol.Debug("[%v] start fetching at: %v", name, lastReadMessage+1)
		store.Fetch(FetchRequest{
			Partition:     "myMessages",
			StartId:       uint64(lastReadMessage + 1),
			Direction:     1,
			Count:         math.MaxInt32,
			MessageC:      msgC,
			ErrorCallback: errorC,
			StartCallback: make(chan int, 1),
		})

	fetch:

		for {
			select {
			case msgAndId, open := <-msgC:
				if !open {
					protocol.Debug("[%v] stop fetching at %v", name, lastReadMessage)
					break fetch
				}
				a.Equal(lastReadMessage+1, int(msgAndId.Id))
				lastReadMessage = int(msgAndId.Id)
			case err := <-errorC:
				a.Fail("received error", err.Error())
				<-done
				return
			}
		}

	}
	protocol.Debug("[%v] ready, got %v", name, lastReadMessage)
	done <- true
}
<|endoftext|>


<commit_before>package transport

import (
	"fmt"
	"io/ioutil"
	"log"
	"time"

	"sync"

	"github.com/achilleasa/usrv"
	"github.com/achilleasa/usrv-service-adapters"
	amqpAdapter "github.com/achilleasa/usrv-service-adapters/service/amqp"
	amqpDrv "github.com/streadway/amqp"
)

// Amqp is a singleton transport instance that works with the amqp service adapter
var Amqp *amqp = &amqp{
	srvAdapter:    amqpAdapter.Adapter,
	logger:        log.New(ioutil.Discard, "", log.LstdFlags),
	closeNotifier: adapters.NewNotifier(),
}

// The Amqp type represents a transport service that can talk to AMQP servers (e.g. rabbitmq)
type amqp struct {

	// A mutex that guards access to the struct fields.
	sync.Mutex

	// The amqp connection adapter
	srvAdapter *amqpAdapter.Amqp

	// The logger for server messages.
	logger *log.Logger

	// AMQP channel handle.
	channel *amqpDrv.Channel

	// A notifier for close events.
	closeNotifier *adapters.Notifier
}

// Connect to the AMQP server.
func (a *amqp) Dial() error {
	a.Lock()
	defer a.Unlock()
	err := a.srvAdapter.Dial()
	if err != nil {
		return usrv.ErrDialFailed
	}

	// Allocate channel
	a.channel, err = a.srvAdapter.NewChannel()
	if err != nil {
		return err
	}

	return nil
}

// Close the transport.
func (a *amqp) Close() {
	a.srvAdapter.Close()
	a.channel = nil
}

// Bind a client or server endpoint to the AMQP server.
//
// For server bindings we use the endpoint name  as a queue to listen to for messages. For
// client bindings a private amqp queue will be allocated so the server can route RPC responses
// to this particular client.
//
func (a *amqp) Bind(bindingType usrv.BindingType, endpoint string) (*usrv.Binding, error) {
	var queue amqpDrv.Queue
	var err error

	if bindingType == usrv.ServerBinding {
		// When running as a server we listen to the queue matching the endpoint name
		queue, err = a.channel.QueueDeclare(
			endpoint,
			false, // durable
			true,  // delete when unused
			false, // exclusive
			false, // noWait
			nil,   // args
		)
	} else {
		// When running as a client we let the server allocate a random exclusive queue for us
		queue, err = a.channel.QueueDeclare(
			"",
			false, // durable
			true,  // delete when unused
			false, // exclusive
			false, // noWait
			nil,   // args
		)
	}

	if err != nil {
		return nil, fmt.Errorf("Error declaring endpoint queue %s: %s\n", endpoint, err)
	}

	a.logger.Printf("[TRANSPORT] Declared queue %s for endpoint: %s\n", queue.Name, endpoint)

	// Create queue consumer
	deliveries, err := a.channel.Consume(
		queue.Name, // name
		queue.Name, // consumerTag (use same as queue name)
		false,      // noAck
		false,      // exclusive
		false,      // noLocal
		false,      // noWait
		nil,        // arguments
	)

	if err != nil {
		return nil, fmt.Errorf("Error consuming from queue %s: %s\n", endpoint, err)
	}

	msgChan := make(chan usrv.TransportMessage)
	go func() {
		for {
			select {
			case d, ok := <-deliveries:

				// Channel closed; exit worker
				if !ok {
					a.logger.Printf("[TRANSPORT] Shutting down listener for endpoint %s\n", endpoint)
					return
				}

				// Setup response writer if this is a server endpoint
				var resWriter *amqpResponseWriter
				if bindingType == usrv.ServerBinding {
					resWriter = &amqpResponseWriter{
						amqp: a,
						message: &usrv.Message{
							Headers:       make(usrv.Header),
							From:          endpoint,  // this endpoint
							To:            d.ReplyTo, // the reply endpoint of the sender
							CorrelationId: d.CorrelationId,
							Timestamp:     time.Now(),
						},
					}
				}

				// Emit a transport message to the msg channel
				msgChan <- usrv.TransportMessage{
					ResponseWriter: resWriter,
					Message: &usrv.Message{
						Headers:       usrv.Header(d.Headers),
						From:          d.AppId,
						To:            endpoint,
						CorrelationId: d.CorrelationId,
						Timestamp:     d.Timestamp,
						Payload:       d.Body,
					},
				}
			}
		}
	}()

	binding := &usrv.Binding{
		Type:     bindingType,
		Name:     queue.Name,
		Messages: msgChan,
	}

	return binding, nil
}

// Send a message.
func (a *amqp) Send(msg *usrv.Message) error {
	err := a.channel.Publish(
		"",
		msg.To,
		false,
		false,
		amqpDrv.Publishing{
			Headers:       amqpDrv.Table(msg.Headers),
			CorrelationId: msg.CorrelationId,
			AppId:         msg.From,
			Timestamp:     msg.Timestamp,
			Body:          msg.Payload,
			ReplyTo:       msg.ReplyTo,
		},
	)

	return err
}

// Register a listener for receiving close notifications. The transport will emit an error and
// close the channel if the transport is cleanly shut down or close the channel if the connection is reset.
func (a *amqp) NotifyClose(c chan error) {
	a.closeNotifier.Add(c)
}

// Set logger.
func (a *amqp) SetLogger(logger *log.Logger) {
	a.logger = logger
}
<commit_msg>AMQP provider should catch undelivered messages sent by clients and report the error upstream<commit_after>package transport

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"time"

	"sync"

	"github.com/achilleasa/usrv"
	"github.com/achilleasa/usrv-service-adapters"
	amqpAdapter "github.com/achilleasa/usrv-service-adapters/service/amqp"
	amqpDrv "github.com/streadway/amqp"
)

// We need to repeat this error so as not to create a dependency loop with usrv
var (
	errServiceUnavailable = errors.New("Service not available")
)

// Amqp is a singleton transport instance that works with the amqp service adapter
var Amqp *amqp = &amqp{
	srvAdapter:    amqpAdapter.Adapter,
	logger:        log.New(ioutil.Discard, "", log.LstdFlags),
	closeNotifier: adapters.NewNotifier(),
}

// The Amqp type represents a transport service that can talk to AMQP servers (e.g. rabbitmq)
type amqp struct {

	// A mutex that guards access to the struct fields.
	sync.Mutex

	// The amqp connection adapter
	srvAdapter *amqpAdapter.Amqp

	// The logger for server messages.
	logger *log.Logger

	// AMQP channel handle.
	channel *amqpDrv.Channel

	// A notifier for close events.
	closeNotifier *adapters.Notifier
}

// Connect to the AMQP server.
func (a *amqp) Dial() error {
	a.Lock()
	defer a.Unlock()
	err := a.srvAdapter.Dial()
	if err != nil {
		return usrv.ErrDialFailed
	}

	// Allocate channel
	a.channel, err = a.srvAdapter.NewChannel()
	if err != nil {
		return err
	}

	return nil
}

// Close the transport.
func (a *amqp) Close() {
	a.srvAdapter.Close()
	a.channel = nil
}

// Bind a client or server endpoint to the AMQP server.
//
// For server bindings we use the endpoint name  as a queue to listen to for messages. For
// client bindings a private amqp queue will be allocated so the server can route RPC responses
// to this particular client.
//
func (a *amqp) Bind(bindingType usrv.BindingType, endpoint string) (*usrv.Binding, error) {
	var queue amqpDrv.Queue
	var err error

	if bindingType == usrv.ServerBinding {
		// When running as a server we listen to the queue matching the endpoint name
		queue, err = a.channel.QueueDeclare(
			endpoint,
			false, // durable
			true,  // delete when unused
			false, // exclusive
			false, // noWait
			nil,   // args
		)
	} else {
		// When running as a client we let the server allocate a random exclusive queue for us
		queue, err = a.channel.QueueDeclare(
			"",
			false, // durable
			true,  // delete when unused
			false, // exclusive
			false, // noWait
			nil,   // args
		)
	}

	if err != nil {
		return nil, fmt.Errorf("Error declaring endpoint queue %s: %s\n", endpoint, err)
	}

	a.logger.Printf("[TRANSPORT] Declared queue %s for endpoint: %s\n", queue.Name, endpoint)

	// Create queue consumer
	deliveries, err := a.channel.Consume(
		queue.Name, // name
		queue.Name, // consumerTag (use same as queue name)
		false,      // noAck
		false,      // exclusive
		false,      // noLocal
		false,      // noWait
		nil,        // arguments
	)

	if err != nil {
		return nil, fmt.Errorf("Error consuming from queue %s: %s\n", endpoint, err)
	}

	// Register listener for undelivered messages if this is a client binding
	returns := make(chan amqpDrv.Return)
	if bindingType == usrv.ClientBinding {
		returns = a.channel.NotifyReturn(returns)
	}

	msgChan := make(chan usrv.TransportMessage)
	go func() {
		for {
			select {
			case r, ok := <-returns:
				// Channel closed; exit worker
				if !ok {
					a.logger.Printf("[TRANSPORT] Shutting down listener for endpoint %s\n", endpoint)
					return
				}

				// Emit a transport message to the msg channel notifying that message delivery failed
				if r.Headers == nil {
					r.Headers = amqpDrv.Table{}
				}
				headers := usrv.Header(r.Headers)
				headers["error"] = errServiceUnavailable.Error()
				msgChan <- usrv.TransportMessage{
					ResponseWriter: nil,

					Message: &usrv.Message{
						Headers:       headers,
						From:          endpoint,
						To:            r.AppId,
						CorrelationId: r.CorrelationId,
						Timestamp:     r.Timestamp,
						Payload:       nil,
					},
				}

			case d, ok := <-deliveries:

				// Channel closed; exit worker
				if !ok {
					a.logger.Printf("[TRANSPORT] Shutting down listener for endpoint %s\n", endpoint)
					return
				}

				// Setup response writer if this is a server endpoint
				var resWriter *amqpResponseWriter
				if bindingType == usrv.ServerBinding {
					resWriter = &amqpResponseWriter{
						amqp: a,
						message: &usrv.Message{
							Headers:       make(usrv.Header),
							From:          endpoint,  // this endpoint
							To:            d.ReplyTo, // the reply endpoint of the sender
							CorrelationId: d.CorrelationId,
							Timestamp:     time.Now(),
						},
					}
				}

				// Emit a transport message to the msg channel
				msgChan <- usrv.TransportMessage{
					ResponseWriter: resWriter,
					Message: &usrv.Message{
						Headers:       usrv.Header(d.Headers),
						From:          d.AppId,
						To:            endpoint,
						CorrelationId: d.CorrelationId,
						Timestamp:     d.Timestamp,
						Payload:       d.Body,
					},
				}
			}
		}
	}()

	binding := &usrv.Binding{
		Type:     bindingType,
		Name:     queue.Name,
		Messages: msgChan,
	}

	return binding, nil
}

// Send a message.
func (a *amqp) Send(msg *usrv.Message) error {
	err := a.channel.Publish(
		"",
		msg.To,
		true, // the server should immediately report undelived messages
		false,
		amqpDrv.Publishing{
			Headers:       amqpDrv.Table(msg.Headers),
			CorrelationId: msg.CorrelationId,
			AppId:         msg.From,
			Timestamp:     msg.Timestamp,
			Body:          msg.Payload,
			ReplyTo:       msg.ReplyTo,
		},
	)

	return err
}

// Register a listener for receiving close notifications. The transport will emit an error and
// close the channel if the transport is cleanly shut down or close the channel if the connection is reset.
func (a *amqp) NotifyClose(c chan error) {
	a.closeNotifier.Add(c)
}

// Set logger.
func (a *amqp) SetLogger(logger *log.Logger) {
	a.logger = logger
}
<|endoftext|>


<commit_before>package api

import (
	"net/http"
	"strconv"

	"github.com/MyHomeworkSpace/api-server/data"
	"github.com/MyHomeworkSpace/api-server/errorlog"

	"github.com/julienschmidt/httprouter"
)

// responses
type classResponse struct {
	Status  string               `json:"status"`
	Classes []data.HomeworkClass `json:"classes"`
}
type singleClassResponse struct {
	Status string             `json:"status"`
	Class  data.HomeworkClass `json:"class"`
}
type hwInfoResponse struct {
	Status  string `json:"status"`
	HWItems int    `json:"hwItems"`
}

func routeClassesGet(w http.ResponseWriter, r *http.Request, p httprouter.Params, c RouteContext) {
	classes, err := data.GetClassesForUser(c.User)
	if err != nil {
		errorlog.LogError("getting list of classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	writeJSON(w, http.StatusOK, classResponse{"ok", classes})
}

func routeClassesGetID(w http.ResponseWriter, r *http.Request, p httprouter.Params, c RouteContext) {
	rows, err := DB.Query("SELECT id, name, teacher, color, sortIndex, userId FROM classes WHERE id = ? AND userId = ?", p.ByName("id"), c.User.ID)
	if err != nil {
		errorlog.LogError("getting class information", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}
	defer rows.Close()

	if !rows.Next() {
		writeJSON(w, http.StatusForbidden, errorResponse{"error", "forbidden"})
		return
	}
	resp := data.HomeworkClass{-1, "", "", "", -1, -1}
	rows.Scan(&resp.ID, &resp.Name, &resp.Teacher, &resp.Color, &resp.SortIndex, &resp.UserID)
	writeJSON(w, http.StatusOK, singleClassResponse{"ok", resp})
}

func routeClassesHWInfo(w http.ResponseWriter, r *http.Request, p httprouter.Params, c RouteContext) {
	rows, err := DB.Query("SELECT COUNT(*) FROM homework WHERE classId = ? AND userId = ?", p.ByName("id"), c.User.ID)
	if err != nil {
		errorlog.LogError("getting class information", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}
	defer rows.Close()

	if !rows.Next() {
		writeJSON(w, http.StatusForbidden, errorResponse{"error", "forbidden"})
		return
	}
	resp := -1
	rows.Scan(&resp)
	writeJSON(w, http.StatusOK, hwInfoResponse{"ok", resp})
}

func routeClassesAdd(w http.ResponseWriter, r *http.Request, p httprouter.Params, c RouteContext) {
	if r.FormValue("name") == "" || r.FormValue("color") == "" {
		writeJSON(w, http.StatusBadRequest, errorResponse{"error", "missing_params"})
		return
	}

	_, err := DB.Exec(
		"INSERT INTO classes(name, teacher, color, sortIndex, userId) VALUES(?, ?, ?, (SELECT * FROM (SELECT COUNT(*) FROM classes WHERE userId = ?) AS sortIndex), ?)",
		r.FormValue("name"), r.FormValue("teacher"), r.FormValue("color"), c.User.ID, c.User.ID,
	)
	if err != nil {
		errorlog.LogError("adding class", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	writeJSON(w, http.StatusOK, statusResponse{"ok"})
}

func routeClassesEdit(w http.ResponseWriter, r *http.Request, p httprouter.Params, c RouteContext) {
	if r.FormValue("id") == "" || r.FormValue("name") == "" || r.FormValue("color") == "" {
		writeJSON(w, http.StatusBadRequest, errorResponse{"error", "missing_params"})
		return
	}

	// check if you are allowed to edit the given id
	idRows, err := DB.Query("SELECT id FROM classes WHERE userId = ? AND id = ?", c.User.ID, r.FormValue("id"))
	if err != nil {
		errorlog.LogError("editing classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}
	defer idRows.Close()
	if !idRows.Next() {
		writeJSON(w, http.StatusForbidden, errorResponse{"error", "forbidden"})
		return
	}

	_, err = DB.Exec(
		"UPDATE classes SET name = ?, teacher = ?, color = ? WHERE id = ?",
		r.FormValue("name"), r.FormValue("teacher"), r.FormValue("color"), r.FormValue("id"),
	)
	if err != nil {
		errorlog.LogError("editing class", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	writeJSON(w, http.StatusOK, statusResponse{"ok"})
}

func routeClassesDelete(w http.ResponseWriter, r *http.Request, p httprouter.Params, c RouteContext) {
	if r.FormValue("id") == "" {
		writeJSON(w, http.StatusBadRequest, errorResponse{"error", "missing_params"})
		return
	}

	id, err := strconv.Atoi(r.FormValue("id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, errorResponse{"error", "invalid_params"})
		return
	}

	// check if you are allowed to delete the given id
	idRows, err := DB.Query("SELECT id FROM classes WHERE userId = ? AND id = ?", c.User.ID, id)
	if err != nil {
		errorlog.LogError("deleting classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}
	if !idRows.Next() {
		writeJSON(w, http.StatusForbidden, errorResponse{"error", "forbidden"})
		return
	}
	idRows.Close()

	// get the user's current classes so that we can renumber the sort indices
	currentClasses := []int{}
	allIDRows, err := DB.Query("SELECT id FROM classes WHERE userId = ? ORDER BY sortIndex ASC", c.User.ID)
	if err != nil {
		errorlog.LogError("deleting classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}
	for allIDRows.Next() {
		id := -1

		err = allIDRows.Scan(&id)
		if err != nil {
			errorlog.LogError("deleting classes", err)
			writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
			return
		}

		currentClasses = append(currentClasses, id)
	}
	allIDRows.Close()

	// use a transaction so that you can't delete just the hw or the class entry - either both or nothing
	tx, err := DB.Begin()

	// delete HW calendar events
	_, err = tx.Exec("DELETE calendar_hwevents FROM calendar_hwevents INNER JOIN homework ON calendar_hwevents.homeworkId = homework.id WHERE homework.classId = ?", id)
	if err != nil {
		tx.Rollback()
		errorlog.LogError("deleting class", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	// delete HW
	_, err = tx.Exec("DELETE FROM homework WHERE classId = ?", id)
	if err != nil {
		tx.Rollback()
		errorlog.LogError("deleting class", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	// delete class
	_, err = tx.Exec("DELETE FROM classes WHERE id = ?", id)
	if err != nil {
		tx.Rollback()
		errorlog.LogError("deleting class", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	// update all other classes
	currentSortIndex := 0
	for _, classID := range currentClasses {
		if classID == id {
			continue
		}

		_, err = tx.Exec("UPDATE classes SET sortIndex = ? WHERE id = ?", currentSortIndex, classID)
		if err != nil {
			tx.Rollback()
			errorlog.LogError("deleting class", err)
			writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
			return
		}

		currentSortIndex++
	}

	// go!
	err = tx.Commit()
	if err != nil {
		errorlog.LogError("deleting class", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	writeJSON(w, http.StatusOK, statusResponse{"ok"})
}

func routeClassesSwap(w http.ResponseWriter, r *http.Request, p httprouter.Params, c RouteContext) {
	if r.FormValue("id1") == "" || r.FormValue("id2") == "" {
		writeJSON(w, http.StatusBadRequest, errorResponse{"error", "missing_params"})
		return
	}

	id1, err := strconv.Atoi(r.FormValue("id1"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, errorResponse{"error", "invalid_params"})
		return
	}
	id2, err := strconv.Atoi(r.FormValue("id2"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, errorResponse{"error", "invalid_params"})
		return
	}

	// get class 1, check it's yours
	class1Row, err := DB.Query("SELECT sortIndex FROM classes WHERE userId = ? AND id = ?", c.User.ID, id1)
	if err != nil {
		errorlog.LogError("deleting classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}
	if !class1Row.Next() {
		writeJSON(w, http.StatusForbidden, errorResponse{"error", "forbidden"})
		return
	}

	class1SortIndex := -1
	class1Row.Scan(&class1SortIndex)
	class1Row.Close()

	// get class 2, check it's yours
	class2Row, err := DB.Query("SELECT sortIndex FROM classes WHERE userId = ? AND id = ?", c.User.ID, id2)
	if err != nil {
		errorlog.LogError("deleting classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}
	if !class2Row.Next() {
		writeJSON(w, http.StatusForbidden, errorResponse{"error", "forbidden"})
		return
	}

	class2SortIndex := -1
	class2Row.Scan(&class2SortIndex)
	class2Row.Close()

	tx, err := DB.Begin()
	if err != nil {
		errorlog.LogError("swapping classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	_, err = tx.Exec("UPDATE classes SET sortIndex = ? WHERE id = ?", class2SortIndex, id1)
	if err != nil {
		errorlog.LogError("swapping classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	_, err = tx.Exec("UPDATE classes SET sortIndex = ? WHERE id = ?", class1SortIndex, id2)
	if err != nil {
		errorlog.LogError("swapping classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	// go!
	err = tx.Commit()
	if err != nil {
		errorlog.LogError("swapping class", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	writeJSON(w, http.StatusOK, statusResponse{"ok"})
}
<commit_msg>api/classes: add missing error checks<commit_after>package api

import (
	"net/http"
	"strconv"

	"github.com/MyHomeworkSpace/api-server/data"
	"github.com/MyHomeworkSpace/api-server/errorlog"

	"github.com/julienschmidt/httprouter"
)

// responses
type classResponse struct {
	Status  string               `json:"status"`
	Classes []data.HomeworkClass `json:"classes"`
}
type singleClassResponse struct {
	Status string             `json:"status"`
	Class  data.HomeworkClass `json:"class"`
}
type hwInfoResponse struct {
	Status  string `json:"status"`
	HWItems int    `json:"hwItems"`
}

func routeClassesGet(w http.ResponseWriter, r *http.Request, p httprouter.Params, c RouteContext) {
	classes, err := data.GetClassesForUser(c.User)
	if err != nil {
		errorlog.LogError("getting list of classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	writeJSON(w, http.StatusOK, classResponse{"ok", classes})
}

func routeClassesGetID(w http.ResponseWriter, r *http.Request, p httprouter.Params, c RouteContext) {
	rows, err := DB.Query("SELECT id, name, teacher, color, sortIndex, userId FROM classes WHERE id = ? AND userId = ?", p.ByName("id"), c.User.ID)
	if err != nil {
		errorlog.LogError("getting class information", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}
	defer rows.Close()

	if !rows.Next() {
		writeJSON(w, http.StatusForbidden, errorResponse{"error", "forbidden"})
		return
	}

	resp := data.HomeworkClass{-1, "", "", "", -1, -1}
	err = rows.Scan(&resp.ID, &resp.Name, &resp.Teacher, &resp.Color, &resp.SortIndex, &resp.UserID)
	if err != nil {
		errorlog.LogError("getting class information", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	writeJSON(w, http.StatusOK, singleClassResponse{"ok", resp})
}

func routeClassesHWInfo(w http.ResponseWriter, r *http.Request, p httprouter.Params, c RouteContext) {
	rows, err := DB.Query("SELECT COUNT(*) FROM homework WHERE classId = ? AND userId = ?", p.ByName("id"), c.User.ID)
	if err != nil {
		errorlog.LogError("getting class information", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}
	defer rows.Close()

	if !rows.Next() {
		writeJSON(w, http.StatusForbidden, errorResponse{"error", "forbidden"})
		return
	}

	resp := -1
	err = rows.Scan(&resp)
	if err != nil {
		errorlog.LogError("getting class information", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	writeJSON(w, http.StatusOK, hwInfoResponse{"ok", resp})
}

func routeClassesAdd(w http.ResponseWriter, r *http.Request, p httprouter.Params, c RouteContext) {
	if r.FormValue("name") == "" || r.FormValue("color") == "" {
		writeJSON(w, http.StatusBadRequest, errorResponse{"error", "missing_params"})
		return
	}

	_, err := DB.Exec(
		"INSERT INTO classes(name, teacher, color, sortIndex, userId) VALUES(?, ?, ?, (SELECT * FROM (SELECT COUNT(*) FROM classes WHERE userId = ?) AS sortIndex), ?)",
		r.FormValue("name"), r.FormValue("teacher"), r.FormValue("color"), c.User.ID, c.User.ID,
	)
	if err != nil {
		errorlog.LogError("adding class", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	writeJSON(w, http.StatusOK, statusResponse{"ok"})
}

func routeClassesEdit(w http.ResponseWriter, r *http.Request, p httprouter.Params, c RouteContext) {
	if r.FormValue("id") == "" || r.FormValue("name") == "" || r.FormValue("color") == "" {
		writeJSON(w, http.StatusBadRequest, errorResponse{"error", "missing_params"})
		return
	}

	// check if you are allowed to edit the given id
	idRows, err := DB.Query("SELECT id FROM classes WHERE userId = ? AND id = ?", c.User.ID, r.FormValue("id"))
	if err != nil {
		errorlog.LogError("editing classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}
	defer idRows.Close()
	if !idRows.Next() {
		writeJSON(w, http.StatusForbidden, errorResponse{"error", "forbidden"})
		return
	}

	_, err = DB.Exec(
		"UPDATE classes SET name = ?, teacher = ?, color = ? WHERE id = ?",
		r.FormValue("name"), r.FormValue("teacher"), r.FormValue("color"), r.FormValue("id"),
	)
	if err != nil {
		errorlog.LogError("editing class", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	writeJSON(w, http.StatusOK, statusResponse{"ok"})
}

func routeClassesDelete(w http.ResponseWriter, r *http.Request, p httprouter.Params, c RouteContext) {
	if r.FormValue("id") == "" {
		writeJSON(w, http.StatusBadRequest, errorResponse{"error", "missing_params"})
		return
	}

	id, err := strconv.Atoi(r.FormValue("id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, errorResponse{"error", "invalid_params"})
		return
	}

	// check if you are allowed to delete the given id
	idRows, err := DB.Query("SELECT id FROM classes WHERE userId = ? AND id = ?", c.User.ID, id)
	if err != nil {
		errorlog.LogError("deleting classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}
	if !idRows.Next() {
		writeJSON(w, http.StatusForbidden, errorResponse{"error", "forbidden"})
		return
	}
	idRows.Close()

	// get the user's current classes so that we can renumber the sort indices
	currentClasses := []int{}
	allIDRows, err := DB.Query("SELECT id FROM classes WHERE userId = ? ORDER BY sortIndex ASC", c.User.ID)
	if err != nil {
		errorlog.LogError("deleting classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}
	for allIDRows.Next() {
		id := -1

		err = allIDRows.Scan(&id)
		if err != nil {
			errorlog.LogError("deleting classes", err)
			writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
			return
		}

		currentClasses = append(currentClasses, id)
	}
	allIDRows.Close()

	// use a transaction so that you can't delete just the hw or the class entry - either both or nothing
	tx, err := DB.Begin()

	// delete HW calendar events
	_, err = tx.Exec("DELETE calendar_hwevents FROM calendar_hwevents INNER JOIN homework ON calendar_hwevents.homeworkId = homework.id WHERE homework.classId = ?", id)
	if err != nil {
		tx.Rollback()
		errorlog.LogError("deleting class", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	// delete HW
	_, err = tx.Exec("DELETE FROM homework WHERE classId = ?", id)
	if err != nil {
		tx.Rollback()
		errorlog.LogError("deleting class", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	// delete class
	_, err = tx.Exec("DELETE FROM classes WHERE id = ?", id)
	if err != nil {
		tx.Rollback()
		errorlog.LogError("deleting class", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	// update all other classes
	currentSortIndex := 0
	for _, classID := range currentClasses {
		if classID == id {
			continue
		}

		_, err = tx.Exec("UPDATE classes SET sortIndex = ? WHERE id = ?", currentSortIndex, classID)
		if err != nil {
			tx.Rollback()
			errorlog.LogError("deleting class", err)
			writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
			return
		}

		currentSortIndex++
	}

	// go!
	err = tx.Commit()
	if err != nil {
		errorlog.LogError("deleting class", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	writeJSON(w, http.StatusOK, statusResponse{"ok"})
}

func routeClassesSwap(w http.ResponseWriter, r *http.Request, p httprouter.Params, c RouteContext) {
	if r.FormValue("id1") == "" || r.FormValue("id2") == "" {
		writeJSON(w, http.StatusBadRequest, errorResponse{"error", "missing_params"})
		return
	}

	id1, err := strconv.Atoi(r.FormValue("id1"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, errorResponse{"error", "invalid_params"})
		return
	}
	id2, err := strconv.Atoi(r.FormValue("id2"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, errorResponse{"error", "invalid_params"})
		return
	}

	// get class 1, check it's yours
	class1Row, err := DB.Query("SELECT sortIndex FROM classes WHERE userId = ? AND id = ?", c.User.ID, id1)
	if err != nil {
		errorlog.LogError("deleting classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}
	if !class1Row.Next() {
		writeJSON(w, http.StatusForbidden, errorResponse{"error", "forbidden"})
		return
	}

	class1SortIndex := -1
	class1Row.Scan(&class1SortIndex)
	class1Row.Close()

	// get class 2, check it's yours
	class2Row, err := DB.Query("SELECT sortIndex FROM classes WHERE userId = ? AND id = ?", c.User.ID, id2)
	if err != nil {
		errorlog.LogError("deleting classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}
	if !class2Row.Next() {
		writeJSON(w, http.StatusForbidden, errorResponse{"error", "forbidden"})
		return
	}

	class2SortIndex := -1
	class2Row.Scan(&class2SortIndex)
	class2Row.Close()

	tx, err := DB.Begin()
	if err != nil {
		errorlog.LogError("swapping classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	_, err = tx.Exec("UPDATE classes SET sortIndex = ? WHERE id = ?", class2SortIndex, id1)
	if err != nil {
		errorlog.LogError("swapping classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	_, err = tx.Exec("UPDATE classes SET sortIndex = ? WHERE id = ?", class1SortIndex, id2)
	if err != nil {
		errorlog.LogError("swapping classes", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	// go!
	err = tx.Commit()
	if err != nil {
		errorlog.LogError("swapping class", err)
		writeJSON(w, http.StatusInternalServerError, errorResponse{"error", "internal_server_error"})
		return
	}

	writeJSON(w, http.StatusOK, statusResponse{"ok"})
}
<|endoftext|>


<commit_before>package async

/*
  Shorthand to List.RunSeries without having to manually create a new
  list, add the routines, etc.
*/
func Series(routines []Routine, callbacks ...Done) {
  l := New()
  l.Multiple(routines...)

  l.RunSeries(callbacks...)
}

/*
  Run all of the Routine functions in a series effect.

  If there is an error, series will immediately exit and trigger the
  callbacks with the error.

  There are no arguments passed between the routines that are used in series.
  It is just for commands that need to run asynchronously without seeing the
  results of its previous routine.

  For example, take a look at one of the tests for this function:
    func TestSeries(t *testing.T) {
      counter := 0

      Status("Calling Series")
      async.Series([]async.Routine{
        func(done async.Done, args ...interface{}) {
          Status("Increasing counter...")
          counter++
          done(nil)
        },
        func(done async.Done, args ...interface{}) {
          Status("Increasing counter...")
          counter++
          done(nil)
        },
        func(done async.Done, args ...interface{}) {
          Status("Increasing counter...")
          counter++
          done(nil)
        },
        func(done async.Done, args ...interface{}) {
          Status("Increasing counter...")
          counter++
          done(nil)
        },
      }, func(err error, results ...interface{}) {
        if err != nil {
          t.Errorf("Unexpected error: %s", err)
          return
        }

        if counter != 4 {
          t.Errorf("Not all routines were completed.")
          return
        }

        Status("Counter: %d", counter)
      })
    }

*/
func (l *List) RunSeries(callbacks ...Done) {
  fall := fallSeries(l, callbacks...)
  next := nextSeries(l, callbacks...)

  l.Wait.Add(l.Len())

  fall(next)
}

func fallSeries(l *List, callbacks ...Done) func(Done, ...interface{}) {
  return func(next Done, args ...interface{}) {
    e := l.Front()
    _, r := l.Remove(e)

    // Run the first waterfall routine and give it the next function, and
    // any arguments that were provided
    go r(next)
    l.Wait.Wait()
  }
}

func nextSeries(l *List, callbacks ...Done) Done {
  fall := fallSeries(l, callbacks...)

  return func(err error, args ...interface{}) {
    next := nextSeries(l, callbacks...)

    l.Wait.Done()
    if err != nil || l.Len() == 0 {
      // Just in case it's an error, let's make sure we've cleared
      // all of the sync.WaitGroup waits that we initiated.
      for i := 0; i < l.Len(); i++ {
        l.Wait.Done()
      }

      // Send the results to the callbacks
      for i := 0; i < len(callbacks); i++ {
        callbacks[i](err)
      }
      return
    }

    // Run the next waterfall routine with any arguments that were provided
    fall(next)
    return
  }
}
<commit_msg>Fix copy pasta mistake<commit_after>package async

/*
  Shorthand to List.RunSeries without having to manually create a new
  list, add the routines, etc.
*/
func Series(routines []Routine, callbacks ...Done) {
  l := New()
  l.Multiple(routines...)

  l.RunSeries(callbacks...)
}

/*
  Run all of the Routine functions in a series effect.

  If there is an error, series will immediately exit and trigger the
  callbacks with the error.

  There are no arguments passed between the routines that are used in series.
  It is just for commands that need to run asynchronously without seeing the
  results of its previous routine.

  For example, take a look at one of the tests for this function:
    func TestSeries(t *testing.T) {
      counter := 0

      Status("Calling Series")
      async.Series([]async.Routine{
        func(done async.Done, args ...interface{}) {
          Status("Increasing counter...")
          counter++
          done(nil)
        },
        func(done async.Done, args ...interface{}) {
          Status("Increasing counter...")
          counter++
          done(nil)
        },
        func(done async.Done, args ...interface{}) {
          Status("Increasing counter...")
          counter++
          done(nil)
        },
        func(done async.Done, args ...interface{}) {
          Status("Increasing counter...")
          counter++
          done(nil)
        },
      }, func(err error, results ...interface{}) {
        if err != nil {
          t.Errorf("Unexpected error: %s", err)
          return
        }

        if counter != 4 {
          t.Errorf("Not all routines were completed.")
          return
        }

        Status("Counter: %d", counter)
      })
    }

*/
func (l *List) RunSeries(callbacks ...Done) {
  fall := fallSeries(l, callbacks...)
  next := nextSeries(l, callbacks...)

  l.Wait.Add(l.Len())

  fall(next)
}

func fallSeries(l *List, callbacks ...Done) func(Done, ...interface{}) {
  return func(next Done, args ...interface{}) {
    e := l.Front()
    _, r := l.Remove(e)

    // Run the first series routine and give it the next function, and
    // any arguments that were provided
    go r(next)
    l.Wait.Wait()
  }
}

func nextSeries(l *List, callbacks ...Done) Done {
  fall := fallSeries(l, callbacks...)

  return func(err error, args ...interface{}) {
    next := nextSeries(l, callbacks...)

    l.Wait.Done()
    if err != nil || l.Len() == 0 {
      // Just in case it's an error, let's make sure we've cleared
      // all of the sync.WaitGroup waits that we initiated.
      for i := 0; i < l.Len(); i++ {
        l.Wait.Done()
      }

      // Send the results to the callbacks
      for i := 0; i < len(callbacks); i++ {
        callbacks[i](err)
      }
      return
    }

    // Run the next series routine with any arguments that were provided
    fall(next)
    return
  }
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"

	"github.com/Sirupsen/logrus"
	"github.com/go-ozzo/ozzo-dbx"
	"github.com/go-ozzo/ozzo-routing"
	"github.com/go-ozzo/ozzo-routing/auth"
	"github.com/go-ozzo/ozzo-routing/content"
	"github.com/go-ozzo/ozzo-routing/cors"
	_ "github.com/lib/pq"
	"github.com/Zhanat87/go/apis"
	"github.com/Zhanat87/go/app"
	"github.com/Zhanat87/go/daos"
	"github.com/Zhanat87/go/errors"
	"github.com/Zhanat87/go/services"
	"os"
	"database/sql"
)

func main() {
	// load application configurations
	if err := app.LoadConfig("./config"); err != nil {
		panic(fmt.Errorf("Invalid application configuration: %s", err))
	}

	// load error messages
	if err := errors.LoadMessages(app.Config.ErrorFile); err != nil {
		panic(fmt.Errorf("Failed to read the error message file: %s", err))
	}

	// create the logger
	logger := logrus.New()

	// connect to the database
	//db, err := dbx.MustOpen("postgres", app.Config.GetDSN())
	db, err := dbx.Open("postgres", app.Config.GetDSN())
	if err != nil {
		// docker compose can't start and docker image can't build, need first timeout for connect
		//time.Sleep(20 * time.Second)
		//db, err = dbx.MustOpen("postgres", app.Config.GetDSN())
		//if err != nil {
			panic(err)
		//}
	}
	db.LogFunc = logger.Infof

	// wire up API routing
	http.Handle("/", buildRouter(logger, db, app.Config.GetDSN()))

	// start the server
	address := fmt.Sprintf(":%v", app.Config.ServerPort)
	logger.Infof("server %v is started at %v\n", app.Version, address)
	panic(http.ListenAndServe(address, nil))
}

func buildRouter(logger *logrus.Logger, db *dbx.DB, dsn string) *routing.Router {
	router := routing.New()

	router.To("GET,HEAD", "/ping", func(c *routing.Context) error {
		c.Abort()  // skip all other middlewares/handlers
		return c.Write("OK " + app.Version)
	})

	router.To("GET,HEAD", "/test", func(c *routing.Context) error {
		c.Abort()  // skip all other middlewares/handlers
		var variables string
		for _, e := range os.Environ() {
			variables += e + "\r\n"
		}
		_, err := sql.Open("postgres", dsn)
		if err != nil {
			variables += err.Error() + "\r\n"
		} else {
			variables += "success connected\r\n"
		}
		return c.Write(variables + "\r\n" + dsn + "\r\ndeploy\r\naot compilation\r\nwebhook2\r\n")
	})

	router.Use(
		app.Init(logger),
		content.TypeNegotiator(content.JSON),
		cors.Handler(cors.Options{
			AllowOrigins: "*",
			AllowHeaders: "*",
			AllowMethods: "*",
		}),
		app.Transactional(db),
	)

	rg := router.Group("/v1")

	rg.Post("/auth", apis.Auth(app.Config.JWTSigningKey))
	rg.Use(auth.JWT(app.Config.JWTVerificationKey, auth.JWTOptions{
		SigningMethod: app.Config.JWTSigningMethod,
		TokenHandler:  apis.JWTHandler,
	}))

	artistDAO := daos.NewArtistDAO()
	apis.ServeArtistResource(rg, services.NewArtistService(artistDAO))

	albumDAO := daos.NewAlbumDAO()
	apis.ServeAlbumResource(rg, services.NewAlbumService(albumDAO))

	userDAO := daos.NewUserDAO()
	apis.ServeUserResource(rg, services.NewUserService(userDAO))

	// wire up more resource APIs here

	return router
}
<commit_msg>deploy<commit_after>package main

import (
	"fmt"
	"net/http"

	"github.com/Sirupsen/logrus"
	"github.com/go-ozzo/ozzo-dbx"
	"github.com/go-ozzo/ozzo-routing"
	"github.com/go-ozzo/ozzo-routing/auth"
	"github.com/go-ozzo/ozzo-routing/content"
	"github.com/go-ozzo/ozzo-routing/cors"
	_ "github.com/lib/pq"
	"github.com/Zhanat87/go/apis"
	"github.com/Zhanat87/go/app"
	"github.com/Zhanat87/go/daos"
	"github.com/Zhanat87/go/errors"
	"github.com/Zhanat87/go/services"
	"os"
	"database/sql"
)

func main() {
	// load application configurations
	if err := app.LoadConfig("./config"); err != nil {
		panic(fmt.Errorf("Invalid application configuration: %s", err))
	}

	// load error messages
	if err := errors.LoadMessages(app.Config.ErrorFile); err != nil {
		panic(fmt.Errorf("Failed to read the error message file: %s", err))
	}

	// create the logger
	logger := logrus.New()

	// connect to the database
	//db, err := dbx.MustOpen("postgres", app.Config.GetDSN())
	db, err := dbx.Open("postgres", app.Config.GetDSN())
	if err != nil {
		// docker compose can't start and docker image can't build, need first timeout for connect
		//time.Sleep(20 * time.Second)
		//db, err = dbx.MustOpen("postgres", app.Config.GetDSN())
		//if err != nil {
			panic(err)
		//}
	}
	db.LogFunc = logger.Infof

	// wire up API routing
	http.Handle("/", buildRouter(logger, db, app.Config.GetDSN()))

	// start the server
	address := fmt.Sprintf(":%v", app.Config.ServerPort)
	logger.Infof("server %v is started at %v\n", app.Version, address)
	panic(http.ListenAndServe(address, nil))
}

func buildRouter(logger *logrus.Logger, db *dbx.DB, dsn string) *routing.Router {
	router := routing.New()

	router.To("GET,HEAD", "/ping", func(c *routing.Context) error {
		c.Abort()  // skip all other middlewares/handlers
		return c.Write("OK " + app.Version)
	})

	router.To("GET,HEAD", "/test", func(c *routing.Context) error {
		c.Abort()  // skip all other middlewares/handlers
		var variables string
		for _, e := range os.Environ() {
			variables += e + "\r\n"
		}
		_, err := sql.Open("postgres", dsn)
		if err != nil {
			variables += err.Error() + "\r\n"
		} else {
			variables += "success connected\r\n"
		}
		return c.Write(variables + "\r\n" + dsn + "\r\ndeploy\r\naot compilation\r\nwebhook22\r\n")
	})

	router.Use(
		app.Init(logger),
		content.TypeNegotiator(content.JSON),
		cors.Handler(cors.Options{
			AllowOrigins: "*",
			AllowHeaders: "*",
			AllowMethods: "*",
		}),
		app.Transactional(db),
	)

	rg := router.Group("/v1")

	rg.Post("/auth", apis.Auth(app.Config.JWTSigningKey))
	rg.Use(auth.JWT(app.Config.JWTVerificationKey, auth.JWTOptions{
		SigningMethod: app.Config.JWTSigningMethod,
		TokenHandler:  apis.JWTHandler,
	}))

	artistDAO := daos.NewArtistDAO()
	apis.ServeArtistResource(rg, services.NewArtistService(artistDAO))

	albumDAO := daos.NewAlbumDAO()
	apis.ServeAlbumResource(rg, services.NewAlbumService(albumDAO))

	userDAO := daos.NewUserDAO()
	apis.ServeUserResource(rg, services.NewUserService(userDAO))

	// wire up more resource APIs here

	return router
}
<|endoftext|>


<commit_before>package main

import (
	"net/http"
	"time"

	"github.com/goware/cors"
	"github.com/pressly/chi"
	"github.com/pressly/chi/middleware"
	"github.com/pressly/chi/render"
)

func initServer(port string, useLog bool) {
	r := chi.NewRouter()

	if useLog {
		r.Use(middleware.Logger)
	}
	r.Use(middleware.RequestID)
	r.Use(middleware.Recoverer)
	// r.Use(middleware.Compress())
	r.Use(middleware.Timeout(60 * time.Second))

	r.Use(render.SetContentType(render.ContentTypeJSON))

	cors := cors.New(cors.Options{
		AllowedOrigins: []string{"http://localhost:8080"}, // Use this to allow specific origin hosts
		// AllowedOrigins:   []string{"*"},
		AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
		ExposedHeaders:   []string{"Link"},
		AllowCredentials: true,
		MaxAge:           300, // Maximum value not ignored by any of major browsers
	})
	r.Use(cors.Handler)

	// r.Get("/", func(w http.ResponseWriter, r *http.Request) {
	// 	w.Write([]byte("root."))
	// })

	//e.File("/", "public/index.html")
	//e.File("/favicon.ico", "public/favicon.ico")
	//e.Static("/static", "public/static")

	//e.POST("/login", login)

	// e.("/login", login)

	r.Route("/contacts", func(r chi.Router) {
		r.Get("/", listContacts)
		r.Post("/", createContact)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getContact)
			r.Put("/", updateContact)
			r.Delete("/", deleteContact)
		})
	})

	r.Route("/companies", func(r chi.Router) {
		// r.With(paginate).Get("/", listCompanies)
		r.Get("/", listCompanies)
		r.Post("/", createCompany)
		// r.Get("/search", SearchArticles)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getCompany)
			r.Put("/", updateCompany)
			r.Delete("/", deleteCompany)
		})
	})

	r.Route("/scopes", func(r chi.Router) {
		r.Get("/", listScopes)
		r.Post("/", createScope)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getScope)
			r.Put("/", updateScope)
			r.Delete("/", deleteScope)
		})
	})

	r.Route("/educations", func(r chi.Router) {
		r.Get("/", listEducations)
		r.Post("/", createEducation)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getEducation)
			r.Put("/", updateEducation)
			r.Delete("/", deleteEducation)
		})
	})

	r.Route("/practices", func(r chi.Router) {
		r.Get("/", listPractices)
		r.Post("/", createPractice)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getPractice)
			r.Put("/", updatePractice)
			r.Delete("/", deletePractice)
		})
	})

	r.Route("/kinds", func(r chi.Router) {
		r.Get("/", listKinds)
		r.Post("/", createKind)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getKind)
			r.Put("/", updateKind)
			r.Delete("/", deleteKind)
		})
	})

	r.Route("/posts", func(r chi.Router) {
		r.Get("/", listPosts)
		r.Post("/", createPost)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getPost)
			r.Put("/", updatePost)
			r.Delete("/", deletePost)
		})
	})

	r.Route("/ranks", func(r chi.Router) {
		r.Get("/", listRanks)
		r.Post("/", createRank)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getRank)
			r.Put("/", updateRank)
			r.Delete("/", deleteRank)
		})
	})

	r.Route("/departments", func(r chi.Router) {
		r.Get("/", listDepartments)
		r.Post("/", createDepartment)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getDepartment)
			r.Put("/", updateDepartment)
			r.Delete("/", deleteDepartment)
		})
	})

	// e.("/about", about)
	// e.File("/favicon.ico", "public/favicon.ico")
	// e.Static("/public", "public")

	err := http.ListenAndServe(":"+port, r)
	errmsg("ListenAndServe", err)
}
<commit_msg>fix path<commit_after>package main

import (
	"net/http"
	"time"

	"github.com/goware/cors"
	"github.com/pressly/chi"
	"github.com/pressly/chi/middleware"
	"github.com/pressly/chi/render"
)

func initServer(port string, useLog bool) {
	r := chi.NewRouter()

	if useLog {
		r.Use(middleware.Logger)
	}
	r.Use(middleware.RequestID)
	r.Use(middleware.Recoverer)
	// r.Use(middleware.Compress())
	r.Use(middleware.Timeout(60 * time.Second))

	r.Use(render.SetContentType(render.ContentTypeJSON))

	cors := cors.New(cors.Options{
		// AllowedOrigins:   []string{"*"},
		AllowedOrigins:   []string{"http://localhost:8080"}, // Use this to allow specific origin hosts
		AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
		ExposedHeaders:   []string{"Link"},
		AllowCredentials: true,
		MaxAge:           300, // Maximum value not ignored by any of major browsers
	})
	r.Use(cors.Handler)

	// r.Get("/", func(w http.ResponseWriter, r *http.Request) {
	// 	w.Write([]byte("root."))
	// })

	//e.File("/", "public/index.html")
	//e.File("/favicon.ico", "public/favicon.ico")
	//e.Static("/static", "public/static")

	//e.POST("/login", login)

	// e.("/login", login)

	r.Route("/api/v1/contacts", func(r chi.Router) {
		r.Get("/", listContacts)
		r.Post("/", createContact)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getContact)
			r.Put("/", updateContact)
			r.Delete("/", deleteContact)
		})
	})

	r.Route("/api/v1/companies", func(r chi.Router) {
		// r.With(paginate).Get("/", listCompanies)
		r.Get("/", listCompanies)
		r.Post("/", createCompany)
		// r.Get("/search", SearchArticles)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getCompany)
			r.Put("/", updateCompany)
			r.Delete("/", deleteCompany)
		})
	})

	r.Route("/api/v1/scopes", func(r chi.Router) {
		r.Get("/", listScopes)
		r.Post("/", createScope)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getScope)
			r.Put("/", updateScope)
			r.Delete("/", deleteScope)
		})
	})

	r.Route("/api/v1/educations", func(r chi.Router) {
		r.Get("/", listEducations)
		r.Post("/", createEducation)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getEducation)
			r.Put("/", updateEducation)
			r.Delete("/", deleteEducation)
		})
	})

	r.Route("/api/v1/practices", func(r chi.Router) {
		r.Get("/", listPractices)
		r.Post("/", createPractice)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getPractice)
			r.Put("/", updatePractice)
			r.Delete("/", deletePractice)
		})
	})

	r.Route("/api/v1/kinds", func(r chi.Router) {
		r.Get("/", listKinds)
		r.Post("/", createKind)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getKind)
			r.Put("/", updateKind)
			r.Delete("/", deleteKind)
		})
	})

	r.Route("/api/v1/posts", func(r chi.Router) {
		r.Get("/", listPosts)
		r.Post("/", createPost)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getPost)
			r.Put("/", updatePost)
			r.Delete("/", deletePost)
		})
	})

	r.Route("/api/v1/ranks", func(r chi.Router) {
		r.Get("/", listRanks)
		r.Post("/", createRank)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getRank)
			r.Put("/", updateRank)
			r.Delete("/", deleteRank)
		})
	})

	r.Route("/api/v1/departments", func(r chi.Router) {
		r.Get("/", listDepartments)
		r.Post("/", createDepartment)
		r.Route("/:id", func(r chi.Router) {
			r.Get("/", getDepartment)
			r.Put("/", updateDepartment)
			r.Delete("/", deleteDepartment)
		})
	})

	// e.("/about", about)
	// e.File("/favicon.ico", "public/favicon.ico")
	// e.Static("/public", "public")

	err := http.ListenAndServe(":"+port, r)
	errmsg("ListenAndServe", err)
}
<|endoftext|>


<commit_before>// Copyright (c) 2013 Joshua Elliott
// Released under the MIT License
// http://opensource.org/licenses/MIT

package turnpike

import (
	"code.google.com/p/go.net/websocket"
	"encoding/json"
	"fmt"
	"github.com/nu7hatch/gouuid"
	"io"
	"log"
	"net"
	"net/http"
	"sync"
	"time"
)

var (
	serverBacklog = 20
)

const (
	clientConnTimeout = 6
	clientMaxFailures = 3
)

// RPCError represents a call error and is the recommended way to return an
// error from a RPC handler.
type RPCError struct {
	URI         string
	Description string
	Details     interface{}
}

// Error returns an error description.
func (e RPCError) Error() string {
	return fmt.Sprintf("turnpike: RPC error with URI %s: %s", e.URI, e.Description)
}

type listenerMap map[string]bool

func (lm listenerMap) add(id string) {
	lm[id] = true
}
func (lm listenerMap) contains(id string) bool {
	return lm[id]
}
func (lm listenerMap) remove(id string) {
	delete(lm, id)
}

// RPCHandler is an interface for that handlers to RPC calls should implement.
// The first parameter is the call ID, the second is the proc URI. Last comes
// all optional arguments to the RPC call. The return can be of any type that
// can be marshaled to JSON, or a error (preferably RPCError but any error works.)
// NOTE: this may be broken in v2 if multiple-return is implemented
type RPCHandler func(string, string, ...interface{}) (interface{}, error)

// Server represents a WAMP server that handles RPC and pub/sub.
type Server struct {
	clients map[string]chan string
	// this is a map because it cheaply prevents a client from subscribing multiple times
	// the nice side-effect here is it's easy to unsubscribe
	//               topicID  clients
	subscriptions map[string]listenerMap
	//           client    prefixes
	prefixes            map[string]prefixMap
	rpcHooks            map[string]RPCHandler
	sessionOpenCallback func(string)
	subLock             *sync.Mutex
	websocket.Server
}

func checkWAMPHandshake(config *websocket.Config, req *http.Request) error {
	for _, protocol := range config.Protocol {
		if protocol == "wamp" {
			config.Protocol = []string{protocol}
			return nil
		}
	}
	return websocket.ErrBadWebSocketProtocol
}

// NewServer creates a new WAMP server.
func NewServer() *Server {
	s := &Server{
		clients:       make(map[string]chan string),
		subscriptions: make(map[string]listenerMap),
		prefixes:      make(map[string]prefixMap),
		rpcHooks:      make(map[string]RPCHandler),
		subLock:       new(sync.Mutex),
	}
	s.Server = websocket.Server{
		Handshake: checkWAMPHandshake,
		Handler:   websocket.Handler(s.HandleWebsocket),
	}
	return s
}

func (t *Server) handlePrefix(id string, msg prefixMsg) {
	if debug {
		log.Print("turnpike: handling prefix message")
	}
	if _, ok := t.prefixes[id]; !ok {
		t.prefixes[id] = make(prefixMap)
	}
	if err := t.prefixes[id].registerPrefix(msg.Prefix, msg.URI); err != nil {
		if debug {
			log.Printf("turnpike: error registering prefix: %s", err)
		}
	}
	if debug {
		log.Printf("turnpike: client %s registered prefix '%s' for URI: %s", id, msg.Prefix, msg.URI)
	}
}

func (t *Server) handleCall(id string, msg callMsg) {
	if debug {
		log.Print("turnpike: handling call message")
	}

	var out string
	var err error

	if f, ok := t.rpcHooks[msg.ProcURI]; ok && f != nil {
		var res interface{}
		res, err = f(id, msg.ProcURI, msg.CallArgs...)
		if err != nil {
			var errorURI, desc string
			var details interface{}
			if er, ok := err.(RPCError); ok {
				errorURI = er.URI
				desc = er.Description
				details = er.Details
			} else {
				errorURI = msg.ProcURI + "#generic-error"
				desc = err.Error()
			}

			if details != nil {
				out, err = createCallError(msg.CallID, errorURI, desc, details)
			} else {
				out, err = createCallError(msg.CallID, errorURI, desc)
			}
		} else {
			out, err = createCallResult(msg.CallID, res)
		}
	} else {
		if debug {
			log.Printf("turnpike: RPC call not registered: %s", msg.ProcURI)
		}
		out, err = createCallError(msg.CallID, "error:notimplemented", "RPC call '%s' not implemented", msg.ProcURI)
	}

	if err != nil {
		// whatever, let the client hang...
		if debug {
			log.Printf("turnpike: error creating callError message: %s", err)
		}
		return
	}
	if client, ok := t.clients[id]; ok {
		client <- out
	}
}

func (t *Server) handleSubscribe(id string, msg subscribeMsg) {
	if debug {
		log.Print("turnpike: handling subscribe message")
	}
	t.subLock.Lock()
	topic := checkCurie(t.prefixes[id], msg.TopicURI)
	if _, ok := t.subscriptions[topic]; !ok {
		t.subscriptions[topic] = make(map[string]bool)
	}
	t.subscriptions[topic].add(id)
	t.subLock.Unlock()
	if debug {
		log.Printf("turnpike: client %s subscribed to topic: %s", id, topic)
	}
}

func (t *Server) handleUnsubscribe(id string, msg unsubscribeMsg) {
	if debug {
		log.Print("turnpike: handling unsubscribe message")
	}
	t.subLock.Lock()
	topic := checkCurie(t.prefixes[id], msg.TopicURI)
	if lm, ok := t.subscriptions[topic]; ok {
		lm.remove(id)
	}
	t.subLock.Unlock()
	if debug {
		log.Printf("turnpike: client %s unsubscribed from topic: %s", id, topic)
	}
}

func (t *Server) handlePublish(id string, msg publishMsg) {
	if debug {
		log.Print("turnpike: handling publish message")
	}
	topic := checkCurie(t.prefixes[id], msg.TopicURI)
	lm, ok := t.subscriptions[topic]
	if !ok {
		return
	}

	out, err := createEvent(topic, msg.Event)
	if err != nil {
		if debug {
			log.Printf("turnpike: error creating event message: %s", err)
		}
		return
	}

	var sendTo []string
	if len(msg.ExcludeList) > 0 || len(msg.EligibleList) > 0 {
		// this is super ugly, but I couldn't think of a better way...
		for tid := range lm {
			include := true
			for _, _tid := range msg.ExcludeList {
				if tid == _tid {
					include = false
					break
				}
			}
			if include {
				sendTo = append(sendTo, tid)
			}
		}

		for _, tid := range msg.EligibleList {
			include := true
			for _, _tid := range sendTo {
				if _tid == tid {
					include = false
					break
				}
			}
			if include {
				sendTo = append(sendTo, tid)
			}
		}
	} else {
		for tid := range lm {
			if tid == id && msg.ExcludeMe {
				continue
			}
			sendTo = append(sendTo, tid)
		}
	}

	for _, tid := range sendTo {
		// we're not locking anything, so we need
		// to make sure the client didn't disconnecct in the
		// last few nanoseconds...
		if client, ok := t.clients[tid]; ok {
			if len(client) == cap(client) {
				<-client
			}
			client <- string(out)
		}
	}
}

// HandleWebsocket implements the go.net/websocket.Handler interface.
func (t *Server) HandleWebsocket(conn *websocket.Conn) {
	defer conn.Close()

	if debug {
		log.Print("turnpike: received websocket connection")
	}

	tid, err := uuid.NewV4()
	if err != nil {
		if debug {
			log.Print("turnpike: could not create unique id, refusing client connection")
		}
		return
	}
	id := tid.String()
	if debug {
		log.Printf("turnpike: client connected: %s", id)
	}

	arr, err := createWelcome(id, turnpikeServerIdent)
	if err != nil {
		if debug {
			log.Print("turnpike: error encoding welcome message")
		}
		return
	}
	if debug {
		log.Printf("turnpike: sending welcome message: %s", arr)
	}
	err = websocket.Message.Send(conn, string(arr))
	if err != nil {
		if debug {
			log.Printf("turnpike: error sending welcome message, aborting connection: %s", err)
		}
		return
	}

	c := make(chan string, serverBacklog)
	t.clients[id] = c

	if t.sessionOpenCallback != nil {
		t.sessionOpenCallback(id)
	}

	failures := 0
	go func() {
		for msg := range c {
			if debug {
				log.Printf("turnpike: sending message: %s", msg)
			}
			conn.SetWriteDeadline(time.Now().Add(clientConnTimeout * time.Second))
			err := websocket.Message.Send(conn, msg)
			if err != nil {
				if nErr, ok := err.(net.Error); ok && (nErr.Timeout() || nErr.Temporary()) {
					log.Printf("Network error: %s", nErr)
					failures++
					if failures > clientMaxFailures {
						break
					}
				} else {
					if debug {
						log.Printf("turnpike: error sending message: %s", err)
					}
					break
				}
			}
		}
		if debug {
			log.Printf("Client %s disconnected", id)
		}
		conn.Close()
	}()

	for {
		var rec string
		err := websocket.Message.Receive(conn, &rec)
		if err != nil {
			if err != io.EOF {
				if debug {
					log.Printf("turnpike: error receiving message, aborting connection: %s", err)
				}
			}
			break
		}
		if debug {
			log.Printf("turnpike: message received: %s", rec)
		}

		data := []byte(rec)

		switch typ := parseMessageType(rec); typ {
		case msgPrefix:
			var msg prefixMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				if debug {
					log.Printf("turnpike: error unmarshalling prefix message: %s", err)
				}
				continue
			}
			t.handlePrefix(id, msg)
		case msgCall:
			var msg callMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				if debug {
					log.Printf("turnpike: error unmarshalling call message: %s", err)
				}
				continue
			}
			t.handleCall(id, msg)
		case msgSubscribe:
			var msg subscribeMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				if debug {
					log.Printf("turnpike: error unmarshalling subscribe message: %s", err)
				}
				continue
			}
			t.handleSubscribe(id, msg)
		case msgUnsubscribe:
			var msg unsubscribeMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				if debug {
					log.Printf("turnpike: error unmarshalling unsubscribe message: %s", err)
				}
				continue
			}
			t.handleUnsubscribe(id, msg)
		case msgPublish:
			var msg publishMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				if debug {
					log.Printf("turnpike: error unmarshalling publish message: %s", err)
				}
				continue
			}
			t.handlePublish(id, msg)
		case msgWelcome, msgCallResult, msgCallError, msgEvent:
			if debug {
				log.Printf("turnpike: server -> client message received, ignored: %s", messageTypeString(typ))
			}
		default:
			if debug {
				log.Printf("turnpike: invalid message format, message dropped: %s", data)
			}
		}
	}

	delete(t.clients, id)
	close(c)
}

// SetSessionOpenCallback adds a callback function that is run when a new session begins.
// The callback function must accept a string argument that is the session ID.
func (t *Server) SetSessionOpenCallback(f func(string)) {
	t.sessionOpenCallback = f
}

// RegisterRPC adds a handler for the RPC named uri.
func (t *Server) RegisterRPC(uri string, f RPCHandler) {
	if f != nil {
		t.rpcHooks[uri] = f
	}
}

// UnregisterRPC removes a handler for the RPC named uri.
func (t *Server) UnregisterRPC(uri string) {
	delete(t.rpcHooks, uri)
}

// SendEvent sends an event with topic directly (not via Client.Publish())
func (t *Server) SendEvent(topic string, event interface{}) {
	t.handlePublish(topic, publishMsg{
		TopicURI: topic,
		Event:    event,
	})
}
<commit_msg>Rename hooks to handlers<commit_after>// Copyright (c) 2013 Joshua Elliott
// Released under the MIT License
// http://opensource.org/licenses/MIT

package turnpike

import (
	"code.google.com/p/go.net/websocket"
	"encoding/json"
	"fmt"
	"github.com/nu7hatch/gouuid"
	"io"
	"log"
	"net"
	"net/http"
	"sync"
	"time"
)

var (
	serverBacklog = 20
)

const (
	clientConnTimeout = 6
	clientMaxFailures = 3
)

// RPCError represents a call error and is the recommended way to return an
// error from a RPC handler.
type RPCError struct {
	URI         string
	Description string
	Details     interface{}
}

// Error returns an error description.
func (e RPCError) Error() string {
	return fmt.Sprintf("turnpike: RPC error with URI %s: %s", e.URI, e.Description)
}

type listenerMap map[string]bool

func (lm listenerMap) add(id string) {
	lm[id] = true
}
func (lm listenerMap) contains(id string) bool {
	return lm[id]
}
func (lm listenerMap) remove(id string) {
	delete(lm, id)
}

// RPCHandler is an interface for that handlers to RPC calls should implement.
// The first parameter is the call ID, the second is the proc URI. Last comes
// all optional arguments to the RPC call. The return can be of any type that
// can be marshaled to JSON, or a error (preferably RPCError but any error works.)
// NOTE: this may be broken in v2 if multiple-return is implemented
type RPCHandler func(string, string, ...interface{}) (interface{}, error)

// Server represents a WAMP server that handles RPC and pub/sub.
type Server struct {
	clients map[string]chan string
	// this is a map because it cheaply prevents a client from subscribing multiple times
	// the nice side-effect here is it's easy to unsubscribe
	//               topicID  clients
	subscriptions map[string]listenerMap
	//           client    prefixes
	prefixes            map[string]prefixMap
	rpcHandlers         map[string]RPCHandler
	sessionOpenCallback func(string)
	subLock             *sync.Mutex
	websocket.Server
}

func checkWAMPHandshake(config *websocket.Config, req *http.Request) error {
	for _, protocol := range config.Protocol {
		if protocol == "wamp" {
			config.Protocol = []string{protocol}
			return nil
		}
	}
	return websocket.ErrBadWebSocketProtocol
}

// NewServer creates a new WAMP server.
func NewServer() *Server {
	s := &Server{
		clients:       make(map[string]chan string),
		subscriptions: make(map[string]listenerMap),
		prefixes:      make(map[string]prefixMap),
		rpcHandlers:   make(map[string]RPCHandler),
		subLock:       new(sync.Mutex),
	}
	s.Server = websocket.Server{
		Handshake: checkWAMPHandshake,
		Handler:   websocket.Handler(s.HandleWebsocket),
	}
	return s
}

func (t *Server) handlePrefix(id string, msg prefixMsg) {
	if debug {
		log.Print("turnpike: handling prefix message")
	}
	if _, ok := t.prefixes[id]; !ok {
		t.prefixes[id] = make(prefixMap)
	}
	if err := t.prefixes[id].registerPrefix(msg.Prefix, msg.URI); err != nil {
		if debug {
			log.Printf("turnpike: error registering prefix: %s", err)
		}
	}
	if debug {
		log.Printf("turnpike: client %s registered prefix '%s' for URI: %s", id, msg.Prefix, msg.URI)
	}
}

func (t *Server) handleCall(id string, msg callMsg) {
	if debug {
		log.Print("turnpike: handling call message")
	}

	var out string
	var err error

	if f, ok := t.rpcHandlers[msg.ProcURI]; ok && f != nil {
		var res interface{}
		res, err = f(id, msg.ProcURI, msg.CallArgs...)
		if err != nil {
			var errorURI, desc string
			var details interface{}
			if er, ok := err.(RPCError); ok {
				errorURI = er.URI
				desc = er.Description
				details = er.Details
			} else {
				errorURI = msg.ProcURI + "#generic-error"
				desc = err.Error()
			}

			if details != nil {
				out, err = createCallError(msg.CallID, errorURI, desc, details)
			} else {
				out, err = createCallError(msg.CallID, errorURI, desc)
			}
		} else {
			out, err = createCallResult(msg.CallID, res)
		}
	} else {
		if debug {
			log.Printf("turnpike: RPC call not registered: %s", msg.ProcURI)
		}
		out, err = createCallError(msg.CallID, "error:notimplemented", "RPC call '%s' not implemented", msg.ProcURI)
	}

	if err != nil {
		// whatever, let the client hang...
		if debug {
			log.Printf("turnpike: error creating callError message: %s", err)
		}
		return
	}
	if client, ok := t.clients[id]; ok {
		client <- out
	}
}

func (t *Server) handleSubscribe(id string, msg subscribeMsg) {
	if debug {
		log.Print("turnpike: handling subscribe message")
	}
	t.subLock.Lock()
	topic := checkCurie(t.prefixes[id], msg.TopicURI)
	if _, ok := t.subscriptions[topic]; !ok {
		t.subscriptions[topic] = make(map[string]bool)
	}
	t.subscriptions[topic].add(id)
	t.subLock.Unlock()
	if debug {
		log.Printf("turnpike: client %s subscribed to topic: %s", id, topic)
	}
}

func (t *Server) handleUnsubscribe(id string, msg unsubscribeMsg) {
	if debug {
		log.Print("turnpike: handling unsubscribe message")
	}
	t.subLock.Lock()
	topic := checkCurie(t.prefixes[id], msg.TopicURI)
	if lm, ok := t.subscriptions[topic]; ok {
		lm.remove(id)
	}
	t.subLock.Unlock()
	if debug {
		log.Printf("turnpike: client %s unsubscribed from topic: %s", id, topic)
	}
}

func (t *Server) handlePublish(id string, msg publishMsg) {
	if debug {
		log.Print("turnpike: handling publish message")
	}
	topic := checkCurie(t.prefixes[id], msg.TopicURI)
	lm, ok := t.subscriptions[topic]
	if !ok {
		return
	}

	out, err := createEvent(topic, msg.Event)
	if err != nil {
		if debug {
			log.Printf("turnpike: error creating event message: %s", err)
		}
		return
	}

	var sendTo []string
	if len(msg.ExcludeList) > 0 || len(msg.EligibleList) > 0 {
		// this is super ugly, but I couldn't think of a better way...
		for tid := range lm {
			include := true
			for _, _tid := range msg.ExcludeList {
				if tid == _tid {
					include = false
					break
				}
			}
			if include {
				sendTo = append(sendTo, tid)
			}
		}

		for _, tid := range msg.EligibleList {
			include := true
			for _, _tid := range sendTo {
				if _tid == tid {
					include = false
					break
				}
			}
			if include {
				sendTo = append(sendTo, tid)
			}
		}
	} else {
		for tid := range lm {
			if tid == id && msg.ExcludeMe {
				continue
			}
			sendTo = append(sendTo, tid)
		}
	}

	for _, tid := range sendTo {
		// we're not locking anything, so we need
		// to make sure the client didn't disconnecct in the
		// last few nanoseconds...
		if client, ok := t.clients[tid]; ok {
			if len(client) == cap(client) {
				<-client
			}
			client <- string(out)
		}
	}
}

// HandleWebsocket implements the go.net/websocket.Handler interface.
func (t *Server) HandleWebsocket(conn *websocket.Conn) {
	defer conn.Close()

	if debug {
		log.Print("turnpike: received websocket connection")
	}

	tid, err := uuid.NewV4()
	if err != nil {
		if debug {
			log.Print("turnpike: could not create unique id, refusing client connection")
		}
		return
	}
	id := tid.String()
	if debug {
		log.Printf("turnpike: client connected: %s", id)
	}

	arr, err := createWelcome(id, turnpikeServerIdent)
	if err != nil {
		if debug {
			log.Print("turnpike: error encoding welcome message")
		}
		return
	}
	if debug {
		log.Printf("turnpike: sending welcome message: %s", arr)
	}
	err = websocket.Message.Send(conn, string(arr))
	if err != nil {
		if debug {
			log.Printf("turnpike: error sending welcome message, aborting connection: %s", err)
		}
		return
	}

	c := make(chan string, serverBacklog)
	t.clients[id] = c

	if t.sessionOpenCallback != nil {
		t.sessionOpenCallback(id)
	}

	failures := 0
	go func() {
		for msg := range c {
			if debug {
				log.Printf("turnpike: sending message: %s", msg)
			}
			conn.SetWriteDeadline(time.Now().Add(clientConnTimeout * time.Second))
			err := websocket.Message.Send(conn, msg)
			if err != nil {
				if nErr, ok := err.(net.Error); ok && (nErr.Timeout() || nErr.Temporary()) {
					log.Printf("Network error: %s", nErr)
					failures++
					if failures > clientMaxFailures {
						break
					}
				} else {
					if debug {
						log.Printf("turnpike: error sending message: %s", err)
					}
					break
				}
			}
		}
		if debug {
			log.Printf("Client %s disconnected", id)
		}
		conn.Close()
	}()

	for {
		var rec string
		err := websocket.Message.Receive(conn, &rec)
		if err != nil {
			if err != io.EOF {
				if debug {
					log.Printf("turnpike: error receiving message, aborting connection: %s", err)
				}
			}
			break
		}
		if debug {
			log.Printf("turnpike: message received: %s", rec)
		}

		data := []byte(rec)

		switch typ := parseMessageType(rec); typ {
		case msgPrefix:
			var msg prefixMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				if debug {
					log.Printf("turnpike: error unmarshalling prefix message: %s", err)
				}
				continue
			}
			t.handlePrefix(id, msg)
		case msgCall:
			var msg callMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				if debug {
					log.Printf("turnpike: error unmarshalling call message: %s", err)
				}
				continue
			}
			t.handleCall(id, msg)
		case msgSubscribe:
			var msg subscribeMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				if debug {
					log.Printf("turnpike: error unmarshalling subscribe message: %s", err)
				}
				continue
			}
			t.handleSubscribe(id, msg)
		case msgUnsubscribe:
			var msg unsubscribeMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				if debug {
					log.Printf("turnpike: error unmarshalling unsubscribe message: %s", err)
				}
				continue
			}
			t.handleUnsubscribe(id, msg)
		case msgPublish:
			var msg publishMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				if debug {
					log.Printf("turnpike: error unmarshalling publish message: %s", err)
				}
				continue
			}
			t.handlePublish(id, msg)
		case msgWelcome, msgCallResult, msgCallError, msgEvent:
			if debug {
				log.Printf("turnpike: server -> client message received, ignored: %s", messageTypeString(typ))
			}
		default:
			if debug {
				log.Printf("turnpike: invalid message format, message dropped: %s", data)
			}
		}
	}

	delete(t.clients, id)
	close(c)
}

// SetSessionOpenCallback adds a callback function that is run when a new session begins.
// The callback function must accept a string argument that is the session ID.
func (t *Server) SetSessionOpenCallback(f func(string)) {
	t.sessionOpenCallback = f
}

// RegisterRPC adds a handler for the RPC named uri.
func (t *Server) RegisterRPC(uri string, f RPCHandler) {
	if f != nil {
		t.rpcHandlers[uri] = f
	}
}

// UnregisterRPC removes a handler for the RPC named uri.
func (t *Server) UnregisterRPC(uri string) {
	delete(t.rpcHandlers, uri)
}

// SendEvent sends an event with topic directly (not via Client.Publish())
func (t *Server) SendEvent(topic string, event interface{}) {
	t.handlePublish(topic, publishMsg{
		TopicURI: topic,
		Event:    event,
	})
}
<|endoftext|>