<commit_before>package main

import (
	// General
	"github.com/golang/glog"
	"flag"
	"github.com/iambc/xerrors"
	"reflect"
	"os"
	"time"

	//API
	"net/http"
	"encoding/json"

	//DB
	"database/sql"
	_ "github.com/lib/pq"
)

/*
TODO:
2) Add different input/output formats for the API
6) quote of the day
*/

type image_board_clusters struct {
    Id int
    Descr string
    LongDescr string
    BoardLimitCount int
}

type boards struct {
    Id int
    Name string
    Descr string
    ImageBoardClusterId string
    MaxThreadCount int //to be checked in insert thread
    MaxActiveThreadCount int //to be checked  in insert thread
    MaxPostsPerThread int // to be checked in insert thread
    AreAttachmentsAllowed bool // to be checked in insert post
    PostLimitsReachedActionId int // to be checked in insert post
}

type threads struct{
    Id int
    Name string
    Descr string
    BoardId int
    MaxPostsPerThread int
    AreAttachmentsAllowed bool
    LimitsReachedActionId int
}

type thread_posts struct{
    Id int
    Body string
    ThreadId int
    AttachmentUrl *string
    InsertedAt time.Time
    SourceIp *string
}

type thread_limits_reached_actions struct{
    Id	    int
    Name    string
    Descr   string
}

type api_request struct{
    Status  string
    Msg	    *string
    Payload interface{}
}

func getBoards(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
	return []byte{}, xerrors.NewSysErr()
    }

    values := req.URL.Query()
    api_key := values[`api_key`][0]
    rows, err := dbh.Query("select b.id, b.name, b.descr from boards b join image_board_clusters ibc on ibc.id = b.image_board_cluster_id where api_key = $1;", api_key)
    if err != nil {
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `002`, true)
    }
    defer rows.Close()

    var curr_boards []boards
    for rows.Next() {
	var board boards
	err = rows.Scan(&board.Id, &board.Name, &board.Descr)
	if err != nil {
	    return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `003`, true)
	}
	curr_boards = append(curr_boards, board)
    }
    bytes, err1 := json.Marshal(api_request{"ok", nil, &curr_boards})
    if err1 != nil {
	return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `004`, true)
    }
    return bytes, nil
}

func getActiveThreadsForBoard(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    board_id, is_passed := values[`board_id`]
    if !is_passed {
	return []byte{}, xerrors.NewUIErr(`Invalid params: No board_id given!`, `Invalid params: No board_id given!`, `005`, true)
    }

    api_key := values[`api_key`][0]
    rows, err := dbh.Query(`select t.id, t.name from threads t
				join boards b on b.id = t.board_id
				join image_board_clusters ibc on ibc.id = b.image_board_cluster_id
			    where t.is_active = TRUE and t.board_id = $1 and ibc.api_key = $2;`, board_id[0], api_key)
    if err != nil {
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `006`, true)
    }
    defer rows.Close()

    var active_threads []threads
    for rows.Next() {
	glog.Info("Popped new thread")
        var thread threads
        err = rows.Scan(&thread.Id, &thread.Name)
        if err != nil {
            return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `007`, true)
        }
        active_threads = append(active_threads, thread)
    }
    var bytes []byte
    var err1 error
    if(len(active_threads) == 0){
        errMsg := "No objects returned."
        bytes, err1 = json.Marshal(api_request{"error", &errMsg, &active_threads})
    }else {
        bytes, err1 = json.Marshal(api_request{"ok", nil, &active_threads})
    }

    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `008`, true)
    }

    return bytes, nil
}

func getPostsForThread(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    thread_id, is_passed := values[`thread_id`]
    if !is_passed {
        return []byte{},xerrors.NewUIErr(`Invalid params: No thread_id given!`, `Invalid params: No thread_id given!`, `006`, true)
    }

    api_key := values[`api_key`][0]
    rows, err := dbh.Query(`select tp.id, tp.body, tp.attachment_url, tp.inserted_at, tp.source_ip
			    from thread_posts tp join threads t on t.id = tp.thread_id
						 join boards b on b.id = t.board_id
						 join image_board_clusters ibc on ibc.id = b.image_board_cluster_id
			    where tp.thread_id = $1 and ibc.api_key = $2 and t.is_active = true;`, thread_id[0], api_key)
    if err != nil {
	glog.Error(err)
        return []byte{}, xerrors.NewSysErr()
    }
    defer rows.Close()

    var curr_posts []thread_posts
    for rows.Next() {
	glog.Info("new post for thread with id: ", thread_id[0])
        var curr_post thread_posts
        err = rows.Scan(&curr_post.Id, &curr_post.Body, &curr_post.AttachmentUrl, &curr_post.InsertedAt, &curr_post.SourceIp)
        if err != nil {
	    glog.Error(err)
            return []byte{}, xerrors.NewSysErr()
        }
        curr_posts = append(curr_posts, curr_post)
    }

    var bytes []byte
    var err1 error
    if(len(curr_posts) == 0){
	errMsg := "No objects returned."
	bytes, err1 = json.Marshal(api_request{"error", &errMsg, &curr_posts})
    }else {
	bytes, err1 = json.Marshal(api_request{"ok", nil, &curr_posts})
    }

    if err1 != nil {
        return []byte{}, xerrors.NewSysErr()
    }

    return bytes, nil
}

func addPostToThread(res http.ResponseWriter, req *http.Request) ([]byte,error) {
    if req == nil || res == nil{
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    thread_id, is_passed := values[`thread_id`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_id given!`, `Invalid params: No thread_id given!`, `001`, true)
    }

    thread_body_post, is_passed := values[`thread_post_body`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_post_body given!`, `Invalid params: No thread_post_body given!`, `001`, true)
    }

    var is_limit_reached bool
    err := dbh.QueryRow("select (select count(*) from thread_posts  where thread_id = $1) > max_posts_per_thread  from threads where id = $1;", thread_id[0]).Scan(&is_limit_reached)
    if err != nil {
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `009`, true)
    }

    if is_limit_reached {
	dbh.QueryRow("UPDATE threads set is_active = false where id = $1",  thread_id[0]).Scan()
	return []byte{}, xerrors.NewUIErr(`Thread post limit reached!`, `Thread post limit reached!`, `010`, true)
    }

    attachment_urls, is_passed := values[`attachment_url`]
    var attachment_url *string
    if !is_passed{
	attachment_url = nil
    }else{
	attachment_url = &attachment_urls[0]
    }

    if(*attachment_url == ``){
	attachment_url = nil
    }

    _, err = dbh.Query("INSERT INTO thread_posts(body, thread_id, attachment_url, source_ip) VALUES($1, $2, $3, $4)", thread_body_post[0], thread_id[0], attachment_url, req.RemoteAddr)

    if err != nil {
	glog.Error(err)
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `011`, true)
    }

    bytes, err1 := json.Marshal(api_request{"ok", nil, nil})
    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `012`, true)
    }

    return bytes, nil
}

func addThread(res http.ResponseWriter, req *http.Request) ([]byte,error) {
    if req == nil || res == nil{
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()

    thread_name, is_passed := values[`thread_name`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_name given!`, `Invalid params: No thread_name given!`, `013`, true)
    }

    board_id, is_passed := values[`board_id`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No board_id given!`, `Invalid params: No board_id given!`, `014`, true)
    }

    var is_limit_reached bool
    err := dbh.QueryRow("select (select count(*) from threads  where board_id = $1) > thread_setting_max_thread_count  from boards where id = $1;", board_id[0]).Scan(&is_limit_reached)
    if err != nil {
	glog.Error("COULD NOT SELECT thread_count")
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `015`, true)
    }
    if is_limit_reached {
	return []byte{}, xerrors.NewUIErr(`Thread limit reached!`, `Thread limit reached!`, `016`, true)
    }

    _, err = dbh.Query("INSERT INTO threads(name, board_id, limits_reached_action_id, max_posts_per_thread) VALUES($1, $2, 1, 10)", thread_name[0], board_id[0])

    if err != nil {
	glog.Error("INSERT FAILED")
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `017`, true)
    }

    bytes, err1 := json.Marshal(api_request{"ok", nil, nil})
    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `018`, true)
    }

    return bytes, nil
}

var dbConnString = ``
var dbh *sql.DB

// sample usage
func main() {
    flag.Parse()

    var err error
    dbConnString = os.Getenv("ABC_DB_CONN_STRING") // DB will return error if empty string
    dbh, err = sql.Open("postgres", dbConnString)
    if err != nil {
	glog.Fatal(err)
    }

    commands := map[string]func(http.ResponseWriter, *http.Request) ([]byte, error){
				"getBoards": getBoards,
				"getActiveThreadsForBoard": getActiveThreadsForBoard,
				"getPostsForThread": getPostsForThread,
				"addPostToThread": addPostToThread,
				"addThread": addThread,
			       }

    http.HandleFunc("/api", func(res http.ResponseWriter, req *http.Request) {
					values := req.URL.Query()
					command, is_passed := values[`command`]
					if !is_passed {
					    res.Write([]byte(`{"Status":"error","Msg":"Paremeter 'command' is undefined.","Payload":null}`))
					    return
					}

					_, is_passed = values[`api_key`]
					if !is_passed {
					    res.Write([]byte(`{"Status":"error","Msg":"Paremeter 'api_key' is undefined.","Payload":null}`))
					    return
					}

					_, is_passed = commands[command[0]]
					if !is_passed{
					    res.Write([]byte(`{"Status":"error","Msg":"No such command exists.","Payload":null}`))
					    glog.Error("command: ", command[0])
					    return
					}

					res.Header().Set("Access-Control-Allow-Origin", "*")
					bytes, err := commands[command[0]](res, req)

					if err != nil{
					    if string(reflect.TypeOf(err).Name())  == `SysErr` {
						res.Write([]byte(`{"Status":"`+ err.(xerrors.XError).Code +`","Msg":"` + err.Error()  +`","Payload":null}`))
					    } else if string(reflect.TypeOf(err).Name())  == `UIErr` {
						res.Write([]byte(`{"Status":"` + err.(xerrors.XError).Code + `","Msg":"`+ err.Error() +`","Payload":null}`))
					    } else {
						res.Write([]byte(`{"Status":"000","Msg":"Application Error!","Payload":null}`))
					    }
					    glog.Error(err)
					    return
					}

					glog.Info(string(bytes))
					res.Write(bytes)
    })

    http.Handle("/f/", http.StripPrefix("/f/", http.FileServer(http.Dir(os.Getenv("ABC_FILES_DIR")))))

    http.ListenAndServe(`:`+ os.Getenv("ABC_SERVER_ENDPOINT_URL"), nil)
}

<commit_msg>feat: api sends thread post count and thread attachment count<commit_after>package main

import (
	// General
	"github.com/golang/glog"
	"flag"
	"github.com/iambc/xerrors"
	"reflect"
	"os"
	"time"

	//API
	"net/http"
	"encoding/json"

	//DB
	"database/sql"
	_ "github.com/lib/pq"
)

/*
TODO:
2) Add different input/output formats for the API
6) quote of the day
*/

type image_board_clusters struct {
    Id int
    Descr string
    LongDescr string
    BoardLimitCount int
}

type boards struct {
    Id int
    Name string
    Descr string
    ImageBoardClusterId string
    MaxThreadCount int //to be checked in insert thread
    MaxActiveThreadCount int //to be checked  in insert thread
    MaxPostsPerThread int // to be checked in insert thread
    AreAttachmentsAllowed bool // to be checked in insert post
    PostLimitsReachedActionId int // to be checked in insert post
}

type threads struct{
    Id int
    Name string
    Descr string
    BoardId int
    MaxPostsPerThread int
    AreAttachmentsAllowed bool
    LimitsReachedActionId int
    PostCount int
    PostCountWithAttachment int
}

type thread_posts struct{
    Id int
    Body string
    ThreadId int
    AttachmentUrl *string
    InsertedAt time.Time
    SourceIp *string
}

type thread_limits_reached_actions struct{
    Id	    int
    Name    string
    Descr   string
}

type api_request struct{
    Status  string
    Msg	    *string
    Payload interface{}
}

func getBoards(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
	return []byte{}, xerrors.NewSysErr()
    }

    values := req.URL.Query()
    api_key := values[`api_key`][0]
    rows, err := dbh.Query("select b.id, b.name, b.descr from boards b join image_board_clusters ibc on ibc.id = b.image_board_cluster_id where api_key = $1;", api_key)
    if err != nil {
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `002`, true)
    }
    defer rows.Close()

    var curr_boards []boards
    for rows.Next() {
	var board boards
	err = rows.Scan(&board.Id, &board.Name, &board.Descr)
	if err != nil {
	    return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `003`, true)
	}
	curr_boards = append(curr_boards, board)
    }
    bytes, err1 := json.Marshal(api_request{"ok", nil, &curr_boards})
    if err1 != nil {
	return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `004`, true)
    }
    return bytes, nil
}

func getActiveThreadsForBoard(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    board_id, is_passed := values[`board_id`]
    if !is_passed {
	return []byte{}, xerrors.NewUIErr(`Invalid params: No board_id given!`, `Invalid params: No board_id given!`, `005`, true)
    }

    api_key := values[`api_key`][0]
    rows, err := dbh.Query(`select t.id, t.name, count(*), (select count(*) from thread_posts where thread_id = t.id and attachment_url is not null) from threads t
				join boards b on b.id = t.board_id
				join image_board_clusters ibc on ibc.id = b.image_board_cluster_id
				left join thread_posts tp on tp.thread_id = t.id
			    where t.is_active = TRUE and t.board_id = $1 and ibc.api_key = $2 group by 1,2 order by t.id;`, board_id[0], api_key)
    if err != nil {
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `006`, true)
    }
    defer rows.Close()

    var active_threads []threads
    for rows.Next() {
	glog.Info("Popped new thread")
        var thread threads
        err = rows.Scan(&thread.Id, &thread.Name, &thread.PostCount, &thread.PostCountWithAttachment)
        if err != nil {
            return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `007`, true)
        }
        active_threads = append(active_threads, thread)
    }
    var bytes []byte
    var err1 error
    if(len(active_threads) == 0){
        errMsg := "No objects returned."
        bytes, err1 = json.Marshal(api_request{"error", &errMsg, &active_threads})
    }else {
        bytes, err1 = json.Marshal(api_request{"ok", nil, &active_threads})
    }

    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `008`, true)
    }

    return bytes, nil
}

func getPostsForThread(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    thread_id, is_passed := values[`thread_id`]
    if !is_passed {
        return []byte{},xerrors.NewUIErr(`Invalid params: No thread_id given!`, `Invalid params: No thread_id given!`, `006`, true)
    }

    api_key := values[`api_key`][0]
    rows, err := dbh.Query(`select tp.id, tp.body, tp.attachment_url, tp.inserted_at, tp.source_ip
			    from thread_posts tp join threads t on t.id = tp.thread_id
						 join boards b on b.id = t.board_id
						 join image_board_clusters ibc on ibc.id = b.image_board_cluster_id
			    where tp.thread_id = $1 and ibc.api_key = $2 and t.is_active = true;`, thread_id[0], api_key)
    if err != nil {
	glog.Error(err)
        return []byte{}, xerrors.NewSysErr()
    }
    defer rows.Close()

    var curr_posts []thread_posts
    for rows.Next() {
	glog.Info("new post for thread with id: ", thread_id[0])
        var curr_post thread_posts
        err = rows.Scan(&curr_post.Id, &curr_post.Body, &curr_post.AttachmentUrl, &curr_post.InsertedAt, &curr_post.SourceIp)
        if err != nil {
	    glog.Error(err)
            return []byte{}, xerrors.NewSysErr()
        }
        curr_posts = append(curr_posts, curr_post)
    }

    var bytes []byte
    var err1 error
    if(len(curr_posts) == 0){
	errMsg := "No objects returned."
	bytes, err1 = json.Marshal(api_request{"error", &errMsg, &curr_posts})
    }else {
	bytes, err1 = json.Marshal(api_request{"ok", nil, &curr_posts})
    }

    if err1 != nil {
        return []byte{}, xerrors.NewSysErr()
    }

    return bytes, nil
}

func addPostToThread(res http.ResponseWriter, req *http.Request) ([]byte,error) {
    if req == nil || res == nil{
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    thread_id, is_passed := values[`thread_id`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_id given!`, `Invalid params: No thread_id given!`, `001`, true)
    }

    thread_body_post, is_passed := values[`thread_post_body`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_post_body given!`, `Invalid params: No thread_post_body given!`, `001`, true)
    }

    var is_limit_reached bool
    err := dbh.QueryRow("select (select count(*) from thread_posts  where thread_id = $1) > max_posts_per_thread  from threads where id = $1;", thread_id[0]).Scan(&is_limit_reached)
    if err != nil {
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `009`, true)
    }

    if is_limit_reached {
	dbh.QueryRow("UPDATE threads set is_active = false where id = $1",  thread_id[0]).Scan()
	return []byte{}, xerrors.NewUIErr(`Thread post limit reached!`, `Thread post limit reached!`, `010`, true)
    }

    attachment_urls, is_passed := values[`attachment_url`]
    var attachment_url *string
    if !is_passed{
	attachment_url = nil
    }else{
	attachment_url = &attachment_urls[0]
    }

    if(*attachment_url == ``){
	attachment_url = nil
    }

    _, err = dbh.Query("INSERT INTO thread_posts(body, thread_id, attachment_url, source_ip) VALUES($1, $2, $3, $4)", thread_body_post[0], thread_id[0], attachment_url, req.RemoteAddr)

    if err != nil {
	glog.Error(err)
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `011`, true)
    }

    bytes, err1 := json.Marshal(api_request{"ok", nil, nil})
    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `012`, true)
    }

    return bytes, nil
}

func addThread(res http.ResponseWriter, req *http.Request) ([]byte,error) {
    if req == nil || res == nil{
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()

    thread_name, is_passed := values[`thread_name`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_name given!`, `Invalid params: No thread_name given!`, `013`, true)
    }

    board_id, is_passed := values[`board_id`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No board_id given!`, `Invalid params: No board_id given!`, `014`, true)
    }

    var is_limit_reached bool
    err := dbh.QueryRow("select (select count(*) from threads  where board_id = $1) > thread_setting_max_thread_count  from boards where id = $1;", board_id[0]).Scan(&is_limit_reached)
    if err != nil {
	glog.Error("COULD NOT SELECT thread_count")
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `015`, true)
    }
    if is_limit_reached {
	return []byte{}, xerrors.NewUIErr(`Thread limit reached!`, `Thread limit reached!`, `016`, true)
    }

    _, err = dbh.Query("INSERT INTO threads(name, board_id, limits_reached_action_id, max_posts_per_thread) VALUES($1, $2, 1, 10)", thread_name[0], board_id[0])

    if err != nil {
	glog.Error("INSERT FAILED")
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `017`, true)
    }

    bytes, err1 := json.Marshal(api_request{"ok", nil, nil})
    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `018`, true)
    }

    return bytes, nil
}

var dbConnString = ``
var dbh *sql.DB

// sample usage
func main() {
    flag.Parse()

    var err error
    dbConnString = os.Getenv("ABC_DB_CONN_STRING") // DB will return error if empty string
    dbh, err = sql.Open("postgres", dbConnString)
    if err != nil {
	glog.Fatal(err)
    }

    commands := map[string]func(http.ResponseWriter, *http.Request) ([]byte, error){
				"getBoards": getBoards,
				"getActiveThreadsForBoard": getActiveThreadsForBoard,
				"getPostsForThread": getPostsForThread,
				"addPostToThread": addPostToThread,
				"addThread": addThread,
			       }

    http.HandleFunc("/api", func(res http.ResponseWriter, req *http.Request) {
					values := req.URL.Query()
					command, is_passed := values[`command`]
					if !is_passed {
					    res.Write([]byte(`{"Status":"error","Msg":"Paremeter 'command' is undefined.","Payload":null}`))
					    return
					}

					_, is_passed = values[`api_key`]
					if !is_passed {
					    res.Write([]byte(`{"Status":"error","Msg":"Paremeter 'api_key' is undefined.","Payload":null}`))
					    return
					}

					_, is_passed = commands[command[0]]
					if !is_passed{
					    res.Write([]byte(`{"Status":"error","Msg":"No such command exists.","Payload":null}`))
					    glog.Error("command: ", command[0])
					    return
					}

					res.Header().Set("Access-Control-Allow-Origin", "*")
					bytes, err := commands[command[0]](res, req)

					if err != nil{
					    if string(reflect.TypeOf(err).Name())  == `SysErr` {
						res.Write([]byte(`{"Status":"`+ err.(xerrors.XError).Code +`","Msg":"` + err.Error()  +`","Payload":null}`))
					    } else if string(reflect.TypeOf(err).Name())  == `UIErr` {
						res.Write([]byte(`{"Status":"` + err.(xerrors.XError).Code + `","Msg":"`+ err.Error() +`","Payload":null}`))
					    } else {
						res.Write([]byte(`{"Status":"000","Msg":"Application Error!","Payload":null}`))
					    }
					    glog.Error(err)
					    return
					}

					glog.Info(string(bytes))
					res.Write(bytes)
    })

    http.Handle("/f/", http.StripPrefix("/f/", http.FileServer(http.Dir(os.Getenv("ABC_FILES_DIR")))))

    http.ListenAndServe(`:`+ os.Getenv("ABC_SERVER_ENDPOINT_URL"), nil)
}

<|endoftext|>


<commit_before>// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.

package server

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"strings"

	"github.com/alexjlockwood/gcm"
	"github.com/braintree/manners"
	"github.com/gorilla/mux"
	"github.com/kyokomi/emoji"
	apns "github.com/sideshow/apns2"
	"github.com/sideshow/apns2/certificate"
	"github.com/sideshow/apns2/payload"
	"gopkg.in/throttled/throttled.v1"
	throttledStore "gopkg.in/throttled/throttled.v1/store"
)

const (
	HEADER_FORWARDED = "X-Forwarded-For"
	HEADER_REAL_IP   = "X-Real-IP"
)

var appleClient *apns.Client

func Start() {
	LogInfo("Push proxy server is initializing...")

	if len(CfgPP.ApplePushCertPrivate) > 0 {
		appleCert, appleCertErr := certificate.FromPemFile(CfgPP.ApplePushCertPrivate, CfgPP.ApplePushCertPassword)
		if appleCertErr != nil {
			LogCritical(fmt.Sprintf("Failed to load the apple pem cert err=%v", appleCertErr))
		}

		if CfgPP.ApplePushUseDevelopment {
			appleClient = apns.NewClient(appleCert).Development()
		} else {
			appleClient = apns.NewClient(appleCert).Production()
		}
	} else {
		LogError("Apple push notifications not configured.  Mssing ApplePushCertPrivate.")
	}

	if len(CfgPP.AndroidApiKey) == 0 {
		LogError("Android push notifications not configured.  Mssing AndroidApiKey.")
	}

	router := mux.NewRouter()
	var handler http.Handler = router
	vary := throttled.VaryBy{}
	vary.RemoteAddr = false
	vary.Headers = strings.Fields(CfgPP.ThrottleVaryByHeader)
	th := throttled.RateLimit(throttled.PerSec(CfgPP.ThrottlePerSec), &vary, throttledStore.NewMemStore(CfgPP.ThrottleMemoryStoreSize))

	th.DeniedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		LogError(fmt.Sprintf("%v: code=429 ip=%v", r.URL.Path, GetIpAddress(r)))
		throttled.DefaultDeniedHandler.ServeHTTP(w, r)
	})

	handler = th.Throttle(router)

	router.HandleFunc("/", root).Methods("GET")

	r := router.PathPrefix("/api/v1").Subrouter()
	r.HandleFunc("/send_push", handleSendNotification).Methods("POST")

	go func() {
		err := manners.ListenAndServe(CfgPP.ListenAddress, handler)
		if err != nil {
			LogCritical(err.Error())
		}
	}()

	LogInfo("Server is listening on " + CfgPP.ListenAddress)
}

func Stop() {
	LogInfo("Stopping Server...")
	manners.Close()
}

func root(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Mattermost Push Proxy"))
}

func handleSendNotification(w http.ResponseWriter, r *http.Request) {
	msg := PushNotificationFromJson(r.Body)

	if msg == nil {
		LogError("Failed to read message body")
	}

	if len(msg.ServerId) == 0 {
		LogError("Failed because of missing server Id")
	}

	if len(msg.Message) > 2047 {
		msg.Message = msg.Message[0:2046]
	}

	if msg.Platform == PUSH_NOTIFY_APPLE {
		go sendAppleNotification(msg)
	} else if msg.Platform == PUSH_NOTIFY_ANDROID {
		go sendAndroidNotification(msg)
	} else {
		LogError("Missing platform property")
	}
}

func sendAndroidNotification(msg *PushNotification) {
	var data map[string]interface{}
	if msg.Type == PUSH_TYPE_CLEAR {
		data = map[string]interface{}{"type": PUSH_TYPE_CLEAR, "channel_id": msg.ChannelId}
	} else {
		data = map[string]interface{}{"type": PUSH_TYPE_MESSAGE, "message": emoji.Sprint(msg.Message), "channel_id": msg.ChannelId, "channel_name": msg.ChannelName}
	}

	regIDs := []string{msg.DeviceId}
	gcmMsg := gcm.NewMessage(data, regIDs...)

	sender := &gcm.Sender{ApiKey: CfgPP.AndroidApiKey}

	if len(CfgPP.AndroidApiKey) > 0 {
		LogInfo("Sending android push notification")
		resp, err := sender.Send(gcmMsg, 2)

		if err != nil {
			LogError(fmt.Sprintf("Failed to send GCM push sid=%v did=%v err=%v", msg.ServerId, msg.DeviceId, err))
			return
		}

		if resp.Failure > 0 {
			LogError(fmt.Sprintf("Android response failure: %v", resp))
		}
	}
}

func sendAppleNotification(msg *PushNotification) {

	notification := &apns.Notification{}
	notification.DeviceToken = msg.DeviceId
	payload := payload.NewPayload()
	notification.Payload = payload
	notification.Topic = CfgPP.ApplePushTopic
	payload.Badge(msg.Badge)

	if msg.Type != PUSH_TYPE_CLEAR {
		payload.Alert(emoji.Sprint(msg.Message))
		payload.Category(msg.Category)
		payload.Sound("default")
	}

	if len(msg.ChannelId) > 0 {
		payload.Custom("channel_id", msg.ChannelId)
	}

	if len(msg.ChannelName) > 0 {
		payload.Custom("channel_name", msg.ChannelName)
	}

	if appleClient != nil {
		LogInfo("Sending apple push notification")
		res, err := appleClient.Push(notification)
		if err != nil {
			LogError(fmt.Sprintf("Failed to send apple push sid=%v did=%v err=%v", msg.ServerId, msg.DeviceId, err))
		}

		if !res.Sent() {
			LogError(fmt.Sprintf("Failed to send apple push with res ApnsID=%v reason=%v code=%v", res.ApnsID, res.Reason, res.StatusCode))
		}
	}

	// payload := apns.NewPayload()

	// if msg.Type != PUSH_TYPE_CLEAR {
	// 	payload.Alert = emoji.Sprint(msg.Message)
	// 	payload.Badge = msg.Badge
	// 	payload.Category = msg.Category
	// 	payload.Sound = "default"
	// }

	// payload.Badge = msg.Badge

	// pn := apns.NewPushNotification()
	// pn.DeviceToken = msg.DeviceId
	// pn.AddPayload(payload)

	// if len(msg.ChannelId) > 0 {
	// 	pn.Set("channel_id", msg.ChannelId)
	// }

	// if len(msg.ChannelName) > 0 {
	// 	pn.Set("channel_name", msg.ChannelName)
	// }

	// client := apns.NewClient(CfgPP.ApplePushServer, CfgPP.ApplePushCertPublic, CfgPP.ApplePushCertPrivate)

	// LogInfo("Sending apple push notification")
	// resp := client.Send(pn)

	// if resp.Error != nil {
	// 	LogError(fmt.Sprintf("Failed to send apple push sid=%v did=%v err=%v", msg.ServerId, msg.DeviceId, resp.Error))
	// }
}

func LogInfo(msg string) {
	Log("INFO", msg)
}

func LogError(msg string) {
	Log("ERROR", msg)
}

func LogCritical(msg string) {
	Log("CRIT", msg)
	panic(msg)
}

func Log(level string, msg string) {
	log.Printf("%v %v\n", level, msg)
}

func GetIpAddress(r *http.Request) string {
	address := r.Header.Get(HEADER_FORWARDED)

	if len(address) == 0 {
		address = r.Header.Get(HEADER_REAL_IP)
	}

	if len(address) == 0 {
		address, _, _ = net.SplitHostPort(r.RemoteAddr)
	}

	return address
}
<commit_msg>Remvoing unused code<commit_after>// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.

package server

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"strings"

	"github.com/alexjlockwood/gcm"
	"github.com/braintree/manners"
	"github.com/gorilla/mux"
	"github.com/kyokomi/emoji"
	apns "github.com/sideshow/apns2"
	"github.com/sideshow/apns2/certificate"
	"github.com/sideshow/apns2/payload"
	"gopkg.in/throttled/throttled.v1"
	throttledStore "gopkg.in/throttled/throttled.v1/store"
)

const (
	HEADER_FORWARDED = "X-Forwarded-For"
	HEADER_REAL_IP   = "X-Real-IP"
)

var appleClient *apns.Client

func Start() {
	LogInfo("Push proxy server is initializing...")

	if len(CfgPP.ApplePushCertPrivate) > 0 {
		appleCert, appleCertErr := certificate.FromPemFile(CfgPP.ApplePushCertPrivate, CfgPP.ApplePushCertPassword)
		if appleCertErr != nil {
			LogCritical(fmt.Sprintf("Failed to load the apple pem cert err=%v", appleCertErr))
		}

		if CfgPP.ApplePushUseDevelopment {
			appleClient = apns.NewClient(appleCert).Development()
		} else {
			appleClient = apns.NewClient(appleCert).Production()
		}
	} else {
		LogError("Apple push notifications not configured.  Mssing ApplePushCertPrivate.")
	}

	if len(CfgPP.AndroidApiKey) == 0 {
		LogError("Android push notifications not configured.  Mssing AndroidApiKey.")
	}

	router := mux.NewRouter()
	var handler http.Handler = router
	vary := throttled.VaryBy{}
	vary.RemoteAddr = false
	vary.Headers = strings.Fields(CfgPP.ThrottleVaryByHeader)
	th := throttled.RateLimit(throttled.PerSec(CfgPP.ThrottlePerSec), &vary, throttledStore.NewMemStore(CfgPP.ThrottleMemoryStoreSize))

	th.DeniedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		LogError(fmt.Sprintf("%v: code=429 ip=%v", r.URL.Path, GetIpAddress(r)))
		throttled.DefaultDeniedHandler.ServeHTTP(w, r)
	})

	handler = th.Throttle(router)

	router.HandleFunc("/", root).Methods("GET")

	r := router.PathPrefix("/api/v1").Subrouter()
	r.HandleFunc("/send_push", handleSendNotification).Methods("POST")

	go func() {
		err := manners.ListenAndServe(CfgPP.ListenAddress, handler)
		if err != nil {
			LogCritical(err.Error())
		}
	}()

	LogInfo("Server is listening on " + CfgPP.ListenAddress)
}

func Stop() {
	LogInfo("Stopping Server...")
	manners.Close()
}

func root(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Mattermost Push Proxy"))
}

func handleSendNotification(w http.ResponseWriter, r *http.Request) {
	msg := PushNotificationFromJson(r.Body)

	if msg == nil {
		LogError("Failed to read message body")
	}

	if len(msg.ServerId) == 0 {
		LogError("Failed because of missing server Id")
	}

	if len(msg.Message) > 2047 {
		msg.Message = msg.Message[0:2046]
	}

	if msg.Platform == PUSH_NOTIFY_APPLE {
		go sendAppleNotification(msg)
	} else if msg.Platform == PUSH_NOTIFY_ANDROID {
		go sendAndroidNotification(msg)
	} else {
		LogError("Missing platform property")
	}
}

func sendAndroidNotification(msg *PushNotification) {
	var data map[string]interface{}
	if msg.Type == PUSH_TYPE_CLEAR {
		data = map[string]interface{}{"type": PUSH_TYPE_CLEAR, "channel_id": msg.ChannelId}
	} else {
		data = map[string]interface{}{"type": PUSH_TYPE_MESSAGE, "message": emoji.Sprint(msg.Message), "channel_id": msg.ChannelId, "channel_name": msg.ChannelName}
	}

	regIDs := []string{msg.DeviceId}
	gcmMsg := gcm.NewMessage(data, regIDs...)

	sender := &gcm.Sender{ApiKey: CfgPP.AndroidApiKey}

	if len(CfgPP.AndroidApiKey) > 0 {
		LogInfo("Sending android push notification")
		resp, err := sender.Send(gcmMsg, 2)

		if err != nil {
			LogError(fmt.Sprintf("Failed to send GCM push sid=%v did=%v err=%v", msg.ServerId, msg.DeviceId, err))
			return
		}

		if resp.Failure > 0 {
			LogError(fmt.Sprintf("Android response failure: %v", resp))
		}
	}
}

func sendAppleNotification(msg *PushNotification) {

	notification := &apns.Notification{}
	notification.DeviceToken = msg.DeviceId
	payload := payload.NewPayload()
	notification.Payload = payload
	notification.Topic = CfgPP.ApplePushTopic
	payload.Badge(msg.Badge)

	if msg.Type != PUSH_TYPE_CLEAR {
		payload.Alert(emoji.Sprint(msg.Message))
		payload.Category(msg.Category)
		payload.Sound("default")
	}

	if len(msg.ChannelId) > 0 {
		payload.Custom("channel_id", msg.ChannelId)
	}

	if len(msg.ChannelName) > 0 {
		payload.Custom("channel_name", msg.ChannelName)
	}

	if appleClient != nil {
		LogInfo("Sending apple push notification")
		res, err := appleClient.Push(notification)
		if err != nil {
			LogError(fmt.Sprintf("Failed to send apple push sid=%v did=%v err=%v", msg.ServerId, msg.DeviceId, err))
		}

		if !res.Sent() {
			LogError(fmt.Sprintf("Failed to send apple push with res ApnsID=%v reason=%v code=%v", res.ApnsID, res.Reason, res.StatusCode))
		}
	}
}

func LogInfo(msg string) {
	Log("INFO", msg)
}

func LogError(msg string) {
	Log("ERROR", msg)
}

func LogCritical(msg string) {
	Log("CRIT", msg)
	panic(msg)
}

func Log(level string, msg string) {
	log.Printf("%v %v\n", level, msg)
}

func GetIpAddress(r *http.Request) string {
	address := r.Header.Get(HEADER_FORWARDED)

	if len(address) == 0 {
		address = r.Header.Get(HEADER_REAL_IP)
	}

	if len(address) == 0 {
		address, _, _ = net.SplitHostPort(r.RemoteAddr)
	}

	return address
}
<|endoftext|>


<commit_before>// An IMAP server.
package server

import (
	"bytes"
	"crypto/tls"
	"errors"
	"io"
	"log"
	"net"

	"github.com/emersion/go-imap/common"
	"github.com/emersion/go-imap/backend"
	"github.com/emersion/go-imap/responses"
	"github.com/emersion/go-sasl"
)

// A command handler.
type Handler interface {
	common.Parser

	// Handle this command for a given connection.
	//
	// By default, after this function has returned a status response is sent. To
	// prevent this behavior handlers can use ErrStatusResp or ErrNoStatusResp.
	Handle(conn *Conn) error
}

// A connection upgrader. If a Handler is also an Upgrader, the connection will
// be upgraded after the Handler succeeds.
//
// This should only be used by libraries implementing an IMAP extension (e.g.
// COMPRESS).
type Upgrader interface {
	// Upgrade the connection. This method should call conn.Upgrade().
	Upgrade(conn *Conn) error
}

// A function that creates handlers.
type HandlerFactory func() Handler

// A function that creates SASL servers.
type SaslServerFactory func(conn *Conn) sasl.Server

type errStatusResp struct {
	resp *common.StatusResp
}

func (err *errStatusResp) Error() string {
	return ""
}

// ErrStatusResp can be returned by a Handler to replace the default status
// response. The response tag must be empty.
//
// To disable the default status response, use ErrNoStatusResp instead.
func ErrStatusResp(res *common.StatusResp) error {
	return &errStatusResp{res}
}

// ErrNoStatusResp can be returned by a Handler to prevent the default status
// response from being sent.
func ErrNoStatusResp() error {
	return &errStatusResp{nil}
}

// An IMAP server.
type Server struct {
	listener net.Listener
	conns []*Conn

	caps map[string]common.ConnState
	commands map[string]HandlerFactory
	auths map[string]SaslServerFactory

	// This server's backend.
	Backend backend.Backend
	// Backend updates that will be sent to connected clients.
	Updates *backend.Updates
	// This server's TLS configuration.
	TLSConfig *tls.Config
	// Allow authentication over unencrypted connections.
	AllowInsecureAuth bool
	// Print all network activity to STDOUT.
	Debug bool
}

// Get this server's address.
func (s *Server) Addr() net.Addr {
	return s.listener.Addr()
}

func (s *Server) listen() error {
	defer s.listener.Close()

	for {
		c, err := s.listener.Accept()
		if err != nil {
			return err
		}

		conn := newConn(s, c)
		if s.Debug {
			conn.SetDebug(true)
		}

		go s.handleConn(conn)
	}
}

func (s *Server) handleConn(conn *Conn) error {
	s.conns = append(s.conns, conn)
	defer conn.Close()

	// Send greeting
	if err := conn.greet(); err != nil {
		return err
	}

	for {
		if conn.State == common.LogoutState {
			return nil
		}

		conn.Wait()

		fields, err := conn.ReadLine()
		if err == io.EOF || conn.State == common.LogoutState {
			return nil
		}
		if err != nil {
			log.Println("Error reading command:", err)
			return err
		}

		var res *common.StatusResp
		var up Upgrader

		cmd := &common.Command{}
		if err := cmd.Parse(fields); err != nil {
			res = &common.StatusResp{
				Tag: cmd.Tag,
				Type: common.BAD,
				Info: err.Error(),
			}
		} else {
			var err error
			res, up, err = s.handleCommand(cmd, conn)
			if err != nil {
				res = &common.StatusResp{
					Tag: cmd.Tag,
					Type: common.BAD,
					Info: err.Error(),
				}
			}
		}

		if res != nil {
			if err := conn.WriteResp(res); err != nil {
				log.Println("Error writing response:", err)
				continue
			}
		}

		if up != nil && res.Type == common.OK {
			if err := up.Upgrade(conn); err != nil {
				log.Println("Error upgrading connection:", err)
				return err
			}
		}
	}
}

func (s *Server) getCommandHandler(cmd *common.Command) (hdlr Handler, err error) {
	newHandler, ok := s.commands[cmd.Name]
	if !ok {
		err = errors.New("Unknown command")
		return
	}

	hdlr = newHandler()
	err = hdlr.Parse(cmd.Arguments)
	return
}

func (s *Server) handleCommand(cmd *common.Command, conn *Conn) (res *common.StatusResp, up Upgrader, err error) {
	hdlr, err := s.getCommandHandler(cmd)
	if err != nil {
		return
	}

	hdlrErr := hdlr.Handle(conn)
	if statusErr, ok := hdlrErr.(*errStatusResp); ok {
		res = statusErr.resp
	} else if hdlrErr != nil {
		res = &common.StatusResp{
			Type: common.NO,
			Info: hdlrErr.Error(),
		}
	} else {
		res = &common.StatusResp{
			Type: common.OK,
		}
	}

	if res != nil {
		res.Tag = cmd.Tag

		if res.Type == common.OK && res.Info == "" {
			res.Info = cmd.Name + " completed"
		}
	}

	up, _ = hdlr.(Upgrader)
	return
}

func (s *Server) listenUpdates() (err error) {
	updater, ok := s.Backend.(backend.Updater)
	if !ok {
		return
	}
	s.Updates = updater.Updates()

	var update *backend.Update
	var res common.WriterTo
	for {
		// TODO: do not generate response if nobody will receive it

		select {
		case status := <-s.Updates.Statuses:
			update = &status.Update
			res = status.StatusResp
		case mailbox := <-s.Updates.Mailboxes:
			update = &mailbox.Update
			res = &responses.Select{Mailbox: mailbox.MailboxStatus}
		case message := <-s.Updates.Messages:
			update = &message.Update

			ch := make(chan *common.Message)
			go (func() {
				ch <- message.Message
				close(ch)
			})()

			res = &responses.Fetch{Messages: ch}
		case expunge := <-s.Updates.Expunges:
			update = &expunge.Update

			ch := make(chan uint32)
			go (func() {
				ch <- expunge.SeqNum
				close(ch)
			})()

			res = &responses.Expunge{SeqNums: ch}
		}

		// Format response
		b := &bytes.Buffer{}
		w := common.NewWriter(b)
		if err := res.WriteTo(w); err != nil {
			log.Println("WARN: cannot format unlateral update:", err)
		}

		for _, conn := range s.conns {
			if update.Username != "" && (conn.User == nil || conn.User.Username() != update.Username) {
				continue
			}
			if update.Mailbox != "" && (conn.Mailbox == nil || conn.Mailbox.Name() != update.Mailbox) {
				continue
			}
			if conn.silent {
				// If silent is set, do not send message updates
				if _, ok := res.(*responses.Fetch); ok {
					continue
				}
			}

			conn.locker.Lock()
			if _, err := conn.Writer.Write(b.Bytes()); err != nil {
				log.Println("WARN: error sending unilateral update:", err)
			}
			conn.Flush()
			conn.locker.Unlock()
		}
	}
}

func (s *Server) getCaps(currentState common.ConnState) (caps []string) {
	for name, state := range s.caps {
		if currentState & state != 0 {
			caps = append(caps, name)
		}
	}
	return
}

// Stops listening and closes all current connections.
func (s *Server) Close() error {
	if err := s.listener.Close(); err != nil {
		return err
	}

	for _, conn := range s.conns {
		conn.Close()
	}

	return nil
}

// Register a new capability that will be advertised by this server.
//
// This function should not be called directly, it must only be used by
// libraries implementing extensions of the IMAP protocol.
func (s *Server) RegisterCapability(name string, state common.ConnState) {
	s.caps[name] = state
}

// Register a new authentication mechanism for this server.
//
// This function should not be called directly, it must only be used by
// libraries implementing extensions of the IMAP protocol.
func (s *Server) RegisterAuth(name string, f SaslServerFactory) {
	s.auths[name] = f
}

// Register a new command for this server.
//
// This function should not be called directly, it must only be used by
// libraries implementing extensions of the IMAP protocol.
func (s *Server) RegisterCommand(name string, f HandlerFactory) {
	s.commands[name] = f
}

// Create a new IMAP server from an existing listener.
func NewServer(l net.Listener, bkd backend.Backend) *Server {
	s := &Server{
		listener: l,
		caps: map[string]common.ConnState{},
		Backend: bkd,
	}

	s.auths = map[string]SaslServerFactory{
		"PLAIN": func(conn *Conn) sasl.Server {
			return sasl.NewPlainServer(func(identity, username, password string) error {
				if identity != "" && identity != username {
					return errors.New("Identities not supported")
				}

				user, err := bkd.Login(username, password)
				if err != nil {
					return err
				}

				conn.State = common.AuthenticatedState
				conn.User = user
				return nil
			})
		},
	}

	s.commands = map[string]HandlerFactory{
		common.Noop: func() Handler { return &Noop{} },
		common.Capability: func() Handler { return &Capability{} },
		common.Logout: func() Handler { return &Logout{} },

		common.StartTLS: func() Handler { return &StartTLS{} },
		common.Login: func() Handler { return &Login{} },
		common.Authenticate: func() Handler { return &Authenticate{} },

		common.Select: func() Handler { return &Select{} },
		common.Examine: func() Handler {
			hdlr := &Select{}
			hdlr.ReadOnly = true
			return hdlr
		},
		common.Create: func() Handler { return &Create{} },
		common.Delete: func() Handler { return &Delete{} },
		common.Rename: func() Handler { return &Rename{} },
		common.Subscribe: func() Handler { return &Subscribe{} },
		common.Unsubscribe: func() Handler { return &Unsubscribe{} },
		common.List: func() Handler { return &List{} },
		common.Lsub: func() Handler {
			hdlr := &List{}
			hdlr.Subscribed = true
			return hdlr
		},
		common.Status: func() Handler { return &Status{} },
		common.Append: func() Handler { return &Append{} },

		common.Check: func() Handler { return &Check{} },
		common.Close: func() Handler { return &Close{} },
		common.Expunge: func() Handler { return &Expunge{} },
		common.Search: func() Handler { return &Search{} },
		common.Fetch: func() Handler { return &Fetch{} },
		common.Store: func() Handler { return &Store{} },
		common.Copy: func() Handler { return &Copy{} },
		common.Uid: func() Handler { return &Uid{} },
	}

	go s.listen()
	go s.listenUpdates()
	return s
}

func Listen(addr string, bkd backend.Backend) (s *Server, err error) {
	l, err := net.Listen("tcp", addr)
	if err != nil {
		return
	}

	s = NewServer(l, bkd)
	return
}

func ListenTLS(addr string, bkd backend.Backend, tlsConfig *tls.Config) (s *Server, err error) {
	l, err := tls.Listen("tcp", addr, tlsConfig)
	if err != nil {
		return
	}

	s = NewServer(l, bkd)
	return
}
<commit_msg>server: remove conn from list when disconnecting<commit_after>// An IMAP server.
package server

import (
	"bytes"
	"crypto/tls"
	"errors"
	"io"
	"log"
	"net"

	"github.com/emersion/go-imap/common"
	"github.com/emersion/go-imap/backend"
	"github.com/emersion/go-imap/responses"
	"github.com/emersion/go-sasl"
)

// A command handler.
type Handler interface {
	common.Parser

	// Handle this command for a given connection.
	//
	// By default, after this function has returned a status response is sent. To
	// prevent this behavior handlers can use ErrStatusResp or ErrNoStatusResp.
	Handle(conn *Conn) error
}

// A connection upgrader. If a Handler is also an Upgrader, the connection will
// be upgraded after the Handler succeeds.
//
// This should only be used by libraries implementing an IMAP extension (e.g.
// COMPRESS).
type Upgrader interface {
	// Upgrade the connection. This method should call conn.Upgrade().
	Upgrade(conn *Conn) error
}

// A function that creates handlers.
type HandlerFactory func() Handler

// A function that creates SASL servers.
type SaslServerFactory func(conn *Conn) sasl.Server

type errStatusResp struct {
	resp *common.StatusResp
}

func (err *errStatusResp) Error() string {
	return ""
}

// ErrStatusResp can be returned by a Handler to replace the default status
// response. The response tag must be empty.
//
// To disable the default status response, use ErrNoStatusResp instead.
func ErrStatusResp(res *common.StatusResp) error {
	return &errStatusResp{res}
}

// ErrNoStatusResp can be returned by a Handler to prevent the default status
// response from being sent.
func ErrNoStatusResp() error {
	return &errStatusResp{nil}
}

// An IMAP server.
type Server struct {
	listener net.Listener
	conns []*Conn

	caps map[string]common.ConnState
	commands map[string]HandlerFactory
	auths map[string]SaslServerFactory

	// This server's backend.
	Backend backend.Backend
	// Backend updates that will be sent to connected clients.
	Updates *backend.Updates
	// This server's TLS configuration.
	TLSConfig *tls.Config
	// Allow authentication over unencrypted connections.
	AllowInsecureAuth bool
	// Print all network activity to STDOUT.
	Debug bool
}

// Get this server's address.
func (s *Server) Addr() net.Addr {
	return s.listener.Addr()
}

func (s *Server) listen() error {
	defer s.listener.Close()

	for {
		c, err := s.listener.Accept()
		if err != nil {
			return err
		}

		conn := newConn(s, c)
		if s.Debug {
			conn.SetDebug(true)
		}

		go s.handleConn(conn)
	}
}

func (s *Server) handleConn(conn *Conn) error {
	s.conns = append(s.conns, conn)
	defer (func() {
		conn.Close()

		for i, c := range s.conns {
			if c == conn {
				s.conns = append(s.conns[:i], s.conns[i+1:]...)
				break
			}
		}
	})()

	// Send greeting
	if err := conn.greet(); err != nil {
		return err
	}

	for {
		if conn.State == common.LogoutState {
			return nil
		}

		conn.Wait()

		fields, err := conn.ReadLine()
		if err == io.EOF || conn.State == common.LogoutState {
			return nil
		}
		if err != nil {
			log.Println("Error reading command:", err)
			return err
		}

		var res *common.StatusResp
		var up Upgrader

		cmd := &common.Command{}
		if err := cmd.Parse(fields); err != nil {
			res = &common.StatusResp{
				Tag: cmd.Tag,
				Type: common.BAD,
				Info: err.Error(),
			}
		} else {
			var err error
			res, up, err = s.handleCommand(cmd, conn)
			if err != nil {
				res = &common.StatusResp{
					Tag: cmd.Tag,
					Type: common.BAD,
					Info: err.Error(),
				}
			}
		}

		if res != nil {
			if err := conn.WriteResp(res); err != nil {
				log.Println("Error writing response:", err)
				continue
			}
		}

		if up != nil && res.Type == common.OK {
			if err := up.Upgrade(conn); err != nil {
				log.Println("Error upgrading connection:", err)
				return err
			}
		}
	}
}

func (s *Server) getCommandHandler(cmd *common.Command) (hdlr Handler, err error) {
	newHandler, ok := s.commands[cmd.Name]
	if !ok {
		err = errors.New("Unknown command")
		return
	}

	hdlr = newHandler()
	err = hdlr.Parse(cmd.Arguments)
	return
}

func (s *Server) handleCommand(cmd *common.Command, conn *Conn) (res *common.StatusResp, up Upgrader, err error) {
	hdlr, err := s.getCommandHandler(cmd)
	if err != nil {
		return
	}

	hdlrErr := hdlr.Handle(conn)
	if statusErr, ok := hdlrErr.(*errStatusResp); ok {
		res = statusErr.resp
	} else if hdlrErr != nil {
		res = &common.StatusResp{
			Type: common.NO,
			Info: hdlrErr.Error(),
		}
	} else {
		res = &common.StatusResp{
			Type: common.OK,
		}
	}

	if res != nil {
		res.Tag = cmd.Tag

		if res.Type == common.OK && res.Info == "" {
			res.Info = cmd.Name + " completed"
		}
	}

	up, _ = hdlr.(Upgrader)
	return
}

func (s *Server) listenUpdates() (err error) {
	updater, ok := s.Backend.(backend.Updater)
	if !ok {
		return
	}
	s.Updates = updater.Updates()

	var update *backend.Update
	var res common.WriterTo
	for {
		// TODO: do not generate response if nobody will receive it

		select {
		case status := <-s.Updates.Statuses:
			update = &status.Update
			res = status.StatusResp
		case mailbox := <-s.Updates.Mailboxes:
			update = &mailbox.Update
			res = &responses.Select{Mailbox: mailbox.MailboxStatus}
		case message := <-s.Updates.Messages:
			update = &message.Update

			ch := make(chan *common.Message)
			go (func() {
				ch <- message.Message
				close(ch)
			})()

			res = &responses.Fetch{Messages: ch}
		case expunge := <-s.Updates.Expunges:
			update = &expunge.Update

			ch := make(chan uint32)
			go (func() {
				ch <- expunge.SeqNum
				close(ch)
			})()

			res = &responses.Expunge{SeqNums: ch}
		}

		// Format response
		b := &bytes.Buffer{}
		w := common.NewWriter(b)
		if err := res.WriteTo(w); err != nil {
			log.Println("WARN: cannot format unlateral update:", err)
		}

		for _, conn := range s.conns {
			if update.Username != "" && (conn.User == nil || conn.User.Username() != update.Username) {
				continue
			}
			if update.Mailbox != "" && (conn.Mailbox == nil || conn.Mailbox.Name() != update.Mailbox) {
				continue
			}
			if conn.silent {
				// If silent is set, do not send message updates
				if _, ok := res.(*responses.Fetch); ok {
					continue
				}
			}

			conn.locker.Lock()
			if _, err := conn.Writer.Write(b.Bytes()); err != nil {
				log.Println("WARN: error sending unilateral update:", err)
			}
			conn.Flush()
			conn.locker.Unlock()
		}
	}
}

func (s *Server) getCaps(currentState common.ConnState) (caps []string) {
	for name, state := range s.caps {
		if currentState & state != 0 {
			caps = append(caps, name)
		}
	}
	return
}

// Stops listening and closes all current connections.
func (s *Server) Close() error {
	if err := s.listener.Close(); err != nil {
		return err
	}

	for _, conn := range s.conns {
		conn.Close()
	}

	return nil
}

// Register a new capability that will be advertised by this server.
//
// This function should not be called directly, it must only be used by
// libraries implementing extensions of the IMAP protocol.
func (s *Server) RegisterCapability(name string, state common.ConnState) {
	s.caps[name] = state
}

// Register a new authentication mechanism for this server.
//
// This function should not be called directly, it must only be used by
// libraries implementing extensions of the IMAP protocol.
func (s *Server) RegisterAuth(name string, f SaslServerFactory) {
	s.auths[name] = f
}

// Register a new command for this server.
//
// This function should not be called directly, it must only be used by
// libraries implementing extensions of the IMAP protocol.
func (s *Server) RegisterCommand(name string, f HandlerFactory) {
	s.commands[name] = f
}

// Create a new IMAP server from an existing listener.
func NewServer(l net.Listener, bkd backend.Backend) *Server {
	s := &Server{
		listener: l,
		caps: map[string]common.ConnState{},
		Backend: bkd,
	}

	s.auths = map[string]SaslServerFactory{
		"PLAIN": func(conn *Conn) sasl.Server {
			return sasl.NewPlainServer(func(identity, username, password string) error {
				if identity != "" && identity != username {
					return errors.New("Identities not supported")
				}

				user, err := bkd.Login(username, password)
				if err != nil {
					return err
				}

				conn.State = common.AuthenticatedState
				conn.User = user
				return nil
			})
		},
	}

	s.commands = map[string]HandlerFactory{
		common.Noop: func() Handler { return &Noop{} },
		common.Capability: func() Handler { return &Capability{} },
		common.Logout: func() Handler { return &Logout{} },

		common.StartTLS: func() Handler { return &StartTLS{} },
		common.Login: func() Handler { return &Login{} },
		common.Authenticate: func() Handler { return &Authenticate{} },

		common.Select: func() Handler { return &Select{} },
		common.Examine: func() Handler {
			hdlr := &Select{}
			hdlr.ReadOnly = true
			return hdlr
		},
		common.Create: func() Handler { return &Create{} },
		common.Delete: func() Handler { return &Delete{} },
		common.Rename: func() Handler { return &Rename{} },
		common.Subscribe: func() Handler { return &Subscribe{} },
		common.Unsubscribe: func() Handler { return &Unsubscribe{} },
		common.List: func() Handler { return &List{} },
		common.Lsub: func() Handler {
			hdlr := &List{}
			hdlr.Subscribed = true
			return hdlr
		},
		common.Status: func() Handler { return &Status{} },
		common.Append: func() Handler { return &Append{} },

		common.Check: func() Handler { return &Check{} },
		common.Close: func() Handler { return &Close{} },
		common.Expunge: func() Handler { return &Expunge{} },
		common.Search: func() Handler { return &Search{} },
		common.Fetch: func() Handler { return &Fetch{} },
		common.Store: func() Handler { return &Store{} },
		common.Copy: func() Handler { return &Copy{} },
		common.Uid: func() Handler { return &Uid{} },
	}

	go s.listen()
	go s.listenUpdates()
	return s
}

func Listen(addr string, bkd backend.Backend) (s *Server, err error) {
	l, err := net.Listen("tcp", addr)
	if err != nil {
		return
	}

	s = NewServer(l, bkd)
	return
}

func ListenTLS(addr string, bkd backend.Backend, tlsConfig *tls.Config) (s *Server, err error) {
	l, err := tls.Listen("tcp", addr, tlsConfig)
	if err != nil {
		return
	}

	s = NewServer(l, bkd)
	return
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"strings"

	"github.com/admiraldolphin/govhack2017/server/game"
	"github.com/admiraldolphin/govhack2017/server/load"
)

var prettyLifeEvents = map[string]string{
	"le_bankruptcy":     "Bankrupt",
	"le_birth":          "Born",
	"le_census":         "Census",
	"le_convict":        "Convicted",
	"le_court":          "Court",
	"le_health_welfare": "Health/Welfare",
	"le_immigration":    "Immigrated",
	"le_marriage":       "Married",
}

// CreateDeck churns a bunch of people into a card deck.
func CreateDeck(ct *load.Cards, ppl []*load.Person) game.Deck {
	// Precompute different possible traits
	traits := make(map[string]*game.Trait)
	for _, d := range ct.Death {
		traits[d] = &game.Trait{
			Key:   d,
			Name:  strings.Title(strings.TrimPrefix(d, "dc_")),
			Death: true,
		}
	}
	for _, le := range ct.LifeEvent {
		for _, dc := range ct.Decade {
			key := le + "." + dc
			traits[key] = &game.Trait{
				Key:   key,
				Name:  prettyLifeEvents[le] + " in " + dc + "s",
				Death: false,
			}
		}
	}

	// Scan people to make cards & accumulate matching traits
	var pcs []*game.PersonCard
	for _, p := range ppl {
		pc := &game.PersonCard{
			Name:   p.Name,
			Source: p,
		}
		pcs = append(pcs, pc)

		addTrait := func(key string) {
			t := traits[key]
			if t == nil {
				log.Printf("Couldn't find trait %q", key)
				return
			}
			t.PeopleMatching += 1.0
			pc.Traits = append(pc.Traits, t)
		}

		for _, d := range p.Inquest.DeathCauses {
			addTrait(d)
		}

		if p.Birth.Year != "" {
			addTrait("le_birth." + p.Birth.Year[:3] + "0")
		}
		if p.Immigration.Year != "" {
			addTrait("le_immigration." + p.Immigration.Year[:3] + "0")
		}
		if p.Convict.Year != "" {
			addTrait("le_convict." + p.Convict.Year[:3] + "0")
		}
		if p.Bankruptcy.Year != "" {
			addTrait("le_bankruptcy." + p.Bankruptcy.Year[:3] + "0")
		}
		if p.Marriage.Year != "" {
			addTrait("le_marriage." + p.Marriage.Year[:3] + "0")
		}
		if p.Court.Year != "" {
			addTrait("le_court." + p.Court.Year[:3] + "0")
		}
		if p.HealthWelfare.Year != "" {
			addTrait("le_health_welfare." + p.HealthWelfare.Year[:3] + "0")
		}
		if p.Census.Year != "" {
			addTrait("le_census." + p.Census.Year[:3] + "0")
		}
	}

	// Make cards for traits (but only that match someone).
	acs := make([]*game.ActionCard, 0, len(traits))
	for _, t := range traits {
		if t.PeopleMatching < 1 {
			continue
		}

		card := &game.ActionCard{
			Name:  t.Name,
			Trait: t,
		}
		acs = append(acs, card)
		// Add 2 of each death card
		if t.Death {
			acs = append(acs, card)
		}
	}

	// Normalise PeopleMatching values
	for _, t := range traits {
		t.PeopleMatching /= float64(len(pcs))
	}

	log.Printf("Generated %d people cards and %d action cards", len(pcs), len(acs))
	return &game.Hand{
		People:  pcs,
		Actions: acs,
	}
}
<commit_msg>Eliminate dc_misc<commit_after>package main

import (
	"log"
	"strings"

	"github.com/admiraldolphin/govhack2017/server/game"
	"github.com/admiraldolphin/govhack2017/server/load"
)

var prettyLifeEvents = map[string]string{
	"le_bankruptcy":     "Bankrupt",
	"le_birth":          "Born",
	"le_census":         "Census",
	"le_convict":        "Convicted",
	"le_court":          "Court",
	"le_health_welfare": "Health/Welfare",
	"le_immigration":    "Immigrated",
	"le_marriage":       "Married",
}

// CreateDeck churns a bunch of people into a card deck.
func CreateDeck(ct *load.Cards, ppl []*load.Person) game.Deck {
	// Precompute different possible traits
	traits := make(map[string]*game.Trait)
	for _, d := range ct.Death {
		// Eliminate dc_misc
		if d == "dc_misc" {
			continue
		}
		traits[d] = &game.Trait{
			Key:   d,
			Name:  strings.Title(strings.TrimPrefix(d, "dc_")),
			Death: true,
		}
	}
	for _, le := range ct.LifeEvent {
		for _, dc := range ct.Decade {
			key := le + "." + dc
			traits[key] = &game.Trait{
				Key:   key,
				Name:  prettyLifeEvents[le] + " in " + dc + "s",
				Death: false,
			}
		}
	}

	// Scan people to make cards & accumulate matching traits
	var pcs []*game.PersonCard
	for _, p := range ppl {
		// Eliminate dc_misc
		if len(p.Inquest.DeathCauses) == 1 && p.Inquest.DeathCauses[0] == "dc_misc" {
			continue
		}

		pc := &game.PersonCard{
			Name:   p.Name,
			Source: p,
		}
		pcs = append(pcs, pc)

		addTrait := func(key string) {
			t := traits[key]
			if t == nil {
				log.Printf("Couldn't find trait %q", key)
				return
			}
			t.PeopleMatching += 1.0
			pc.Traits = append(pc.Traits, t)
		}

		for _, d := range p.Inquest.DeathCauses {
			// Eliminate dc_misc
			if d == "dc_misc" {
				continue
			}
			addTrait(d)
		}

		if p.Birth.Year != "" {
			addTrait("le_birth." + p.Birth.Year[:3] + "0")
		}
		if p.Immigration.Year != "" {
			addTrait("le_immigration." + p.Immigration.Year[:3] + "0")
		}
		if p.Convict.Year != "" {
			addTrait("le_convict." + p.Convict.Year[:3] + "0")
		}
		if p.Bankruptcy.Year != "" {
			addTrait("le_bankruptcy." + p.Bankruptcy.Year[:3] + "0")
		}
		if p.Marriage.Year != "" {
			addTrait("le_marriage." + p.Marriage.Year[:3] + "0")
		}
		if p.Court.Year != "" {
			addTrait("le_court." + p.Court.Year[:3] + "0")
		}
		if p.HealthWelfare.Year != "" {
			addTrait("le_health_welfare." + p.HealthWelfare.Year[:3] + "0")
		}
		if p.Census.Year != "" {
			addTrait("le_census." + p.Census.Year[:3] + "0")
		}
	}

	// Make cards for traits (but only that match someone).
	acs := make([]*game.ActionCard, 0, len(traits))
	for _, t := range traits {
		if t.PeopleMatching < 1 {
			continue
		}

		card := &game.ActionCard{
			Name:  t.Name,
			Trait: t,
		}
		acs = append(acs, card)
		// Add 2 of each death card
		if t.Death {
			acs = append(acs, card)
		}
	}

	// Normalise PeopleMatching values
	for _, t := range traits {
		t.PeopleMatching /= float64(len(pcs))
	}

	log.Printf("Generated %d people cards and %d action cards", len(pcs), len(acs))
	return &game.Hand{
		People:  pcs,
		Actions: acs,
	}
}
<|endoftext|>


<commit_before>package account

import (
	"context"
	"database/sql"
	"encoding/hex"
	"encoding/json"
	"fmt"

	"chain/cos/txscript"
	"chain/database/pg"
	"chain/errors"
)

// AnnotateTxs adds account data to transactions
func AnnotateTxs(ctx context.Context, txs []map[string]interface{}) error {
	controlMaps := make(map[string][]map[string]interface{})
	var controlPrograms [][]byte
	for _, tx := range txs {
		outs, ok := tx["outputs"].([]interface{})
		if !ok {
			return errors.Wrap(fmt.Errorf("bad outputs type %T", tx["outputs"]))
		}

		for _, out := range outs {
			txOut, ok := out.(map[string]interface{})
			if !ok {
				return errors.Wrap(fmt.Errorf("bad output type %T", out))
			}
			controlString, ok := txOut["control_program"].(string)
			if !ok {
				return errors.Wrap(fmt.Errorf("bad control program type %T", txOut["control_program"]))
			}
			controlProgram, err := hex.DecodeString(controlString)
			if err != nil {
				return err
			}
			controlPrograms = append(controlPrograms, controlProgram)
			controlMaps[string(controlProgram)] = append(controlMaps[string(controlProgram)], txOut)
		}

		ins, ok := tx["inputs"].([]interface{})
		if !ok {
			return errors.Wrap(fmt.Errorf("bad inputs type %T", tx["inputs"]))
		}

		for _, in := range ins {
			txIn, ok := in.(map[string]interface{})
			if !ok {
				return errors.Wrap(fmt.Errorf("bad input type %T", in))
			}
			inputWitness, ok := txIn["input_witness"].([]interface{})
			if !ok {
				return errors.Wrap(fmt.Errorf("bad input witness type %T", txIn["input_witness"]))
			}
			maybeRedeemStr, ok := inputWitness[len(inputWitness)-1].(string)
			if !ok {
				return errors.Wrap(fmt.Errorf("bad input witness item type %T", inputWitness[len(inputWitness)-1]))
			}
			maybeRedeem, err := hex.DecodeString(maybeRedeemStr)
			if err != nil {
				return err
			}
			controlProgram := txscript.RedeemToPkScript(maybeRedeem)
			controlPrograms = append(controlPrograms, controlProgram)
			controlMaps[string(controlProgram)] = append(controlMaps[string(controlProgram)], txIn)
		}
	}

	const q = `
		SELECT signer_id, control_program, alias, tags
		FROM account_control_programs
		LEFT JOIN signers ON signers.id=account_control_programs.signer_id
		LEFT JOIN accounts ON accounts.account_id=signers.id
		WHERE control_program=ANY($1::bytea[])
	`
	var (
		ids      []string
		programs [][]byte
		aliases  []sql.NullString
		tags     []*json.RawMessage
	)
	err := pg.ForQueryRows(ctx, q, pg.Byteas(controlPrograms), func(accountID string, program []byte, alias sql.NullString, accountTags []byte) {
		ids = append(ids, accountID)
		programs = append(programs, program)
		aliases = append(aliases, alias)
		if len(accountTags) > 0 {
			tags = append(tags, (*json.RawMessage)(&accountTags))
		} else {
			tags = append(tags, nil)
		}
	})
	if err != nil {
		return err
	}
	for i := range ids {
		maps := controlMaps[string(programs[i])]
		for _, m := range maps {
			m["account_id"] = ids[i]
			if tags[i] != nil {
				m["account_tags"] = tags[i]
			}
			if aliases[i].Valid {
				m["account_alias"] = aliases[i].String
			}
		}
	}

	return nil
}
<commit_msg>core/account: check bounds of witness on annotate<commit_after>package account

import (
	"context"
	"database/sql"
	"encoding/hex"
	"encoding/json"
	"fmt"

	"chain/cos/txscript"
	"chain/database/pg"
	"chain/errors"
)

// AnnotateTxs adds account data to transactions
func AnnotateTxs(ctx context.Context, txs []map[string]interface{}) error {
	controlMaps := make(map[string][]map[string]interface{})
	var controlPrograms [][]byte
	for _, tx := range txs {
		outs, ok := tx["outputs"].([]interface{})
		if !ok {
			return errors.Wrap(fmt.Errorf("bad outputs type %T", tx["outputs"]))
		}

		for _, out := range outs {
			txOut, ok := out.(map[string]interface{})
			if !ok {
				return errors.Wrap(fmt.Errorf("bad output type %T", out))
			}
			controlString, ok := txOut["control_program"].(string)
			if !ok {
				return errors.Wrap(fmt.Errorf("bad control program type %T", txOut["control_program"]))
			}
			controlProgram, err := hex.DecodeString(controlString)
			if err != nil {
				return err
			}
			controlPrograms = append(controlPrograms, controlProgram)
			controlMaps[string(controlProgram)] = append(controlMaps[string(controlProgram)], txOut)
		}

		ins, ok := tx["inputs"].([]interface{})
		if !ok {
			return errors.Wrap(fmt.Errorf("bad inputs type %T", tx["inputs"]))
		}

		for _, in := range ins {
			txIn, ok := in.(map[string]interface{})
			if !ok {
				return errors.Wrap(fmt.Errorf("bad input type %T", in))
			}
			inputWitness, ok := txIn["input_witness"].([]interface{})
			if !ok {
				return errors.Wrap(fmt.Errorf("bad input witness type %T", txIn["input_witness"]))
			}
			if len(inputWitness) == 0 {
				continue
			}
			maybeRedeemStr, ok := inputWitness[len(inputWitness)-1].(string)
			if !ok {
				return errors.Wrap(fmt.Errorf("bad input witness item type %T", inputWitness[len(inputWitness)-1]))
			}
			maybeRedeem, err := hex.DecodeString(maybeRedeemStr)
			if err != nil {
				return err
			}
			controlProgram := txscript.RedeemToPkScript(maybeRedeem)
			controlPrograms = append(controlPrograms, controlProgram)
			controlMaps[string(controlProgram)] = append(controlMaps[string(controlProgram)], txIn)
		}
	}

	const q = `
		SELECT signer_id, control_program, alias, tags
		FROM account_control_programs
		LEFT JOIN signers ON signers.id=account_control_programs.signer_id
		LEFT JOIN accounts ON accounts.account_id=signers.id
		WHERE control_program=ANY($1::bytea[])
	`
	var (
		ids      []string
		programs [][]byte
		aliases  []sql.NullString
		tags     []*json.RawMessage
	)
	err := pg.ForQueryRows(ctx, q, pg.Byteas(controlPrograms), func(accountID string, program []byte, alias sql.NullString, accountTags []byte) {
		ids = append(ids, accountID)
		programs = append(programs, program)
		aliases = append(aliases, alias)
		if len(accountTags) > 0 {
			tags = append(tags, (*json.RawMessage)(&accountTags))
		} else {
			tags = append(tags, nil)
		}
	})
	if err != nil {
		return err
	}
	for i := range ids {
		maps := controlMaps[string(programs[i])]
		for _, m := range maps {
			m["account_id"] = ids[i]
			if tags[i] != nil {
				m["account_tags"] = tags[i]
			}
			if aliases[i].Valid {
				m["account_alias"] = aliases[i].String
			}
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package server

import "github.com/vinceprignano/bunny/server/endpoint"

type Server struct {
	endpoints map[string]endpoint.Endpoint
}
<commit_msg>Scratch server<commit_after>package server

import (
	"os"
	"time"

	log "github.com/cihub/seelog"
	"github.com/vinceprignano/bunny/server/endpoint"
	"github.com/vinceprignano/bunny/transport"
)

type Server struct {
	Name      string
	endpoints map[string]endpoint.Endpoint
	Transport transport.Transport
}

func (s *Server) Init() {
	select {
	case s.Transport = <-s.Transport.Connect():
		log.Info("[Server] Connected to transport")
	case <-time.After(10 * time.Second):
		log.Critical("[Server] Failed to connect to transport")
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"net"
	"net/http"
	"sync"

	"playctrl"
)

const msgBufSize = 1

var (
	clientPort = flag.Int("clientport", 49132, "Port to listen for client UDP requests")
	chromePort = flag.Int("chromeport", 49133, "Port to send the Chrome extension SSE events.")
	verbose    = flag.Bool("verbose", false, "Turn on verbose logging")

	// Response headers for the SSE request.
	headers = [][2]string{
		{"Content-Type", "text/event-stream"},
		{"Cache-Control", "no-cache"},
		{"Connection", "keep-alive"},
		{"Access-Control-Allow-Origin", "*"},
	}

	commands = make(chan []byte, msgBufSize)

	mu      sync.RWMutex // protects clients
	clients = make(map[chan []byte]bool)
)

// errorf writes an error to a net.Conn and also log.
func errorf(c net.Conn, format string, args ...interface{}) {
	msg := fmt.Sprintf(format, args...)
	log.Println(msg)
	c.Write([]byte(msg))
}

func commandServer(addr *net.UDPAddr) error {
	c, err := net.ListenUDP("udp", addr)
	if err != nil {
		return err
	}

	decoder := json.NewDecoder(c)
	for {
		msg := &playctrl.Message{}
		if err := decoder.Decode(msg); err != nil {
			errorf(c, "Invalid client request. Error: %s", err)
			continue
		}

		if msg.Version == 0 || msg.Value == "" {
			errorf(c, "Must specify protocol version and message value.")
			continue
		}
		if msg.Version != 1 {
			errorf(c, "Unhandled protocol version: %d", msg.Version)
			continue
		}
		if _, ok := playctrl.CmdNames[msg.Value]; !ok {
			errorf(c, "Invalid command: '%s'", msg.Value)
			continue
		}

		j, err := json.Marshal(msg)
		if err != nil {
			errorf(c, "Error re-encoding message: %s", err)
			continue
		}

		commands <- j
	}
	return nil
}

// multicast
func processCommands() {
	for msg := range commands {
		mu.RLock()
		log.Printf("Sending message %s to %d connected client(s).", msg, len(clients))
		for c := range clients {
			c <- msg
		}
		mu.RUnlock()
	}
}

// Unification of http.ResponseWriter, http.Flusher, and http.CloseNotifier
type HTTPWriter interface {
	Header() http.Header
	Write([]byte) (int, error)
	WriteHeader(int)
	Flush()
	CloseNotify() <-chan bool
}

// Responds to requests with server-send events.
func handleBrowserListener(writer http.ResponseWriter, r *http.Request) {
	w, ok := writer.(HTTPWriter)
	if !ok {
		panic("HTTP server does not support Flusher and/or CloseNotifier needed for SSE.")
	}
	closed := w.CloseNotify()
	log.Println("Client connected.")

	c := make(chan []byte, msgBufSize)
	mu.Lock()
	clients[c] = true
	mu.Unlock()

	for _, header := range headers {
		w.Header().Set(header[0], header[1])
	}

loop:
	for {
		select {
		case msg := <-c:
			fmt.Fprintf(w, "data:%s\n\n", msg)
			w.Flush()
		case <-closed:
			log.Println("Closing client connection.")
			break loop
		}
	}

	mu.Lock()
	delete(clients, c)
	mu.Unlock()
}

func main() {
	flag.Parse()

	go processCommands()
	udpAddr := fmt.Sprintf("localhost:%d", *clientPort)
	udp, err := net.ResolveUDPAddr("udp", udpAddr)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("Listening for UDP client requests on", udp)
	go func() { log.Fatal(commandServer(udp)) }()

	mux := http.NewServeMux()
	mux.HandleFunc("/", handleBrowserListener)
	sseAddr := fmt.Sprintf("localhost:%d", *chromePort)
	log.Println("Listening on for Chrome extension on", sseAddr)
	log.Fatal(http.ListenAndServe(sseAddr, mux))
}
<commit_msg>Server: only print each message if -verbose is on.<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"net"
	"net/http"
	"sync"

	"playctrl"
)

const msgBufSize = 1

var (
	clientPort = flag.Int("clientport", 49132, "Port to listen for client UDP requests")
	chromePort = flag.Int("chromeport", 49133, "Port to send the Chrome extension SSE events.")
	verbose    = flag.Bool("verbose", false, "Turn on verbose logging")

	// Response headers for the SSE request.
	headers = [][2]string{
		{"Content-Type", "text/event-stream"},
		{"Cache-Control", "no-cache"},
		{"Connection", "keep-alive"},
		{"Access-Control-Allow-Origin", "*"},
	}

	commands = make(chan []byte, msgBufSize)

	mu      sync.RWMutex // protects clients
	clients = make(map[chan []byte]bool)
)

// errorf writes an error to a net.Conn and also log.
func errorf(c net.Conn, format string, args ...interface{}) {
	msg := fmt.Sprintf(format, args...)
	log.Println(msg)
	c.Write([]byte(msg))
}

func commandServer(addr *net.UDPAddr) error {
	c, err := net.ListenUDP("udp", addr)
	if err != nil {
		return err
	}

	decoder := json.NewDecoder(c)
	for {
		msg := &playctrl.Message{}
		if err := decoder.Decode(msg); err != nil {
			errorf(c, "Invalid client request. Error: %s", err)
			continue
		}

		if msg.Version == 0 || msg.Value == "" {
			errorf(c, "Must specify protocol version and message value.")
			continue
		}
		if msg.Version != 1 {
			errorf(c, "Unhandled protocol version: %d", msg.Version)
			continue
		}
		if _, ok := playctrl.CmdNames[msg.Value]; !ok {
			errorf(c, "Invalid command: '%s'", msg.Value)
			continue
		}

		j, err := json.Marshal(msg)
		if err != nil {
			errorf(c, "Error re-encoding message: %s", err)
			continue
		}

		commands <- j
	}
	return nil
}

// multicast
func processCommands() {
	for msg := range commands {
		mu.RLock()
		if *verbose {
			log.Printf("Sending message %s to %d connected client(s).", msg, len(clients))
		}
		for c := range clients {
			c <- msg
		}
		mu.RUnlock()
	}
}

// Unification of http.ResponseWriter, http.Flusher, and http.CloseNotifier
type HTTPWriter interface {
	Header() http.Header
	Write([]byte) (int, error)
	WriteHeader(int)
	Flush()
	CloseNotify() <-chan bool
}

// Responds to requests with server-send events.
func handleBrowserListener(writer http.ResponseWriter, r *http.Request) {
	w, ok := writer.(HTTPWriter)
	if !ok {
		panic("HTTP server does not support Flusher and/or CloseNotifier needed for SSE.")
	}
	closed := w.CloseNotify()
	log.Println("Client connected.")

	c := make(chan []byte, msgBufSize)
	mu.Lock()
	clients[c] = true
	mu.Unlock()

	for _, header := range headers {
		w.Header().Set(header[0], header[1])
	}

loop:
	for {
		select {
		case msg := <-c:
			fmt.Fprintf(w, "data:%s\n\n", msg)
			w.Flush()
		case <-closed:
			log.Println("Closing client connection.")
			break loop
		}
	}

	mu.Lock()
	delete(clients, c)
	mu.Unlock()
}

func main() {
	flag.Parse()

	go processCommands()
	udpAddr := fmt.Sprintf("localhost:%d", *clientPort)
	udp, err := net.ResolveUDPAddr("udp", udpAddr)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("Listening for UDP client requests on", udp)
	go func() { log.Fatal(commandServer(udp)) }()

	mux := http.NewServeMux()
	mux.HandleFunc("/", handleBrowserListener)
	sseAddr := fmt.Sprintf("localhost:%d", *chromePort)
	log.Println("Listening on for Chrome extension on", sseAddr)
	log.Fatal(http.ListenAndServe(sseAddr, mux))
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"os"
	"strings"
	"time"

	log "github.com/cihub/seelog"
	"github.com/golang/protobuf/proto"
	"github.com/streadway/amqp"

	"github.com/b2aio/typhon/errors"
	"github.com/b2aio/typhon/rabbit"
)

type AMQPServer struct {
	// this is the routing key prefix for all endpoints
	ServiceName        string
	ServiceDescription string
	endpointRegistry   *EndpointRegistry
	connection         *rabbit.RabbitConnection
	notifyConnected    []chan bool

	closeChan chan struct{}
}

func NewAMQPServer() Server {
	return &AMQPServer{
		endpointRegistry: NewEndpointRegistry(),
		connection:       rabbit.NewRabbitConnection(),
		closeChan:        make(chan struct{}),
	}
}

func (s *AMQPServer) Name() string {
	if s == nil {
		return ""
	}
	return s.ServiceName
}

func (s *AMQPServer) Description() string {
	if s == nil {
		return ""
	}
	return s.ServiceDescription
}

func (s *AMQPServer) Init(c *Config) {
	s.ServiceName = c.Name
	s.ServiceDescription = c.Description
}

func (s *AMQPServer) NotifyConnected() chan bool {
	ch := make(chan bool)
	s.notifyConnected = append(s.notifyConnected, ch)
	return ch
}

func (s *AMQPServer) RegisterEndpoint(endpoint *Endpoint) {
	s.endpointRegistry.Register(endpoint)
}

func (s *AMQPServer) DeregisterEndpoint(endpointName string) {
	s.endpointRegistry.Deregister(endpointName)
}

// Run the server, connecting to our transport and serving requests
func (s *AMQPServer) Run() {

	// Connect to AMQP
	select {
	case <-s.connection.Init():
		log.Info("[Server] Connected to RabbitMQ")
		for _, notify := range s.notifyConnected {
			notify <- true
		}
	case <-time.After(10 * time.Second):
		log.Critical("[Server] Failed to connect to RabbitMQ")
		os.Exit(1)
	}

	// Get a delivery channel from the connection
	log.Infof("[Server] Listening for deliveries on %s.#", s.ServiceName)
	deliveries, err := s.connection.Consume(s.ServiceName)
	if err != nil {
		log.Criticalf("[Server] [%s] Failed to consume from Rabbit", s.ServiceName)
	}

	// Handle deliveries
	for {
		select {
		case req := <-deliveries:
			log.Infof("[Server] [%s] Received new delivery", s.ServiceName)
			go s.handleRequest(req)
		case <-s.closeChan:
			// shut down server
			s.connection.Close()
			break
		}
	}

	log.Infof("Exiting")
	log.Flush()
}

func (s *AMQPServer) Close() {
	close(s.closeChan)
}

// handleRequest takes a delivery from AMQP, attempts to process it and return a response
func (s *AMQPServer) handleRequest(delivery amqp.Delivery) {

	// See if we have a matching endpoint for this request
	endpointName := strings.Replace(delivery.RoutingKey, fmt.Sprintf("%s.", s.ServiceName), "", -1)
	endpoint := s.endpointRegistry.Get(endpointName)
	if endpoint == nil {
		log.Errorf("[Server] Endpoint '%s' not found, cannot handle request", endpointName)
		s.respondWithError(delivery, errors.BadRequest("Endpoint not found"))
		return
	}

	// Handle the delivery
	req := NewAMQPRequest(&delivery)
	resp, err := endpoint.HandleRequest(req)
	if err != nil {
		s.respondWithError(delivery, err)
		return
	}
	if resp == nil {
		s.respondWithError(delivery, errors.BadResponse("Handler returned nil"))
		return
	}

	body, err := proto.Marshal(resp)
	if err != nil {
		log.Errorf("[Server] Failed to marshal response")
		s.respondWithError(delivery, errors.BadResponse("Failed to marshal response: "+err.Error()))
		return
	}

	// Build return delivery, and publish
	msg := amqp.Publishing{
		CorrelationId: delivery.CorrelationId,
		Timestamp:     time.Now().UTC(),
		Body:          body,
		Headers: map[string]interface{}{
			"Content-Encoding": "RESPONSE",
		},
	}
	s.connection.Publish("", delivery.ReplyTo, msg)
}

// respondWithError to a delivery, with the provided error
func (s *AMQPServer) respondWithError(delivery amqp.Delivery, err error) {

	// Ensure we have a service error in proto form
	// and marshal this for transmission
	svcErr := errors.Wrap(err)
	b, err := proto.Marshal(errors.Marshal(svcErr))
	if err != nil {
		// shit
	}

	// Construct a return message with an error
	msg := amqp.Publishing{
		CorrelationId: delivery.CorrelationId,
		Timestamp:     time.Now().UTC(),
		Body:          b,
		Headers: map[string]interface{}{
			"Content-Encoding": "ERROR",
		},
	}

	// Publish the error back to the client
	s.connection.Publish("", delivery.ReplyTo, msg)
}
<commit_msg>Fix infinite blank delivery bug - exit on connection close<commit_after>package server

import (
	"fmt"
	"os"
	"strings"
	"time"

	log "github.com/cihub/seelog"
	"github.com/golang/protobuf/proto"
	"github.com/streadway/amqp"

	"github.com/b2aio/typhon/errors"
	"github.com/b2aio/typhon/rabbit"
)

type AMQPServer struct {
	// this is the routing key prefix for all endpoints
	ServiceName        string
	ServiceDescription string
	endpointRegistry   *EndpointRegistry
	connection         *rabbit.RabbitConnection
	notifyConnected    []chan bool

	closeChan chan struct{}
}

func NewAMQPServer() Server {
	return &AMQPServer{
		endpointRegistry: NewEndpointRegistry(),
		connection:       rabbit.NewRabbitConnection(),
		closeChan:        make(chan struct{}),
	}
}

func (s *AMQPServer) Name() string {
	if s == nil {
		return ""
	}
	return s.ServiceName
}

func (s *AMQPServer) Description() string {
	if s == nil {
		return ""
	}
	return s.ServiceDescription
}

func (s *AMQPServer) Init(c *Config) {
	s.ServiceName = c.Name
	s.ServiceDescription = c.Description
}

func (s *AMQPServer) NotifyConnected() chan bool {
	ch := make(chan bool)
	s.notifyConnected = append(s.notifyConnected, ch)
	return ch
}

func (s *AMQPServer) RegisterEndpoint(endpoint *Endpoint) {
	s.endpointRegistry.Register(endpoint)
}

func (s *AMQPServer) DeregisterEndpoint(endpointName string) {
	s.endpointRegistry.Deregister(endpointName)
}

// Run the server, connecting to our transport and serving requests
func (s *AMQPServer) Run() {

	// Connect to AMQP
	select {
	case <-s.connection.Init():
		log.Info("[Server] Connected to RabbitMQ")
		for _, notify := range s.notifyConnected {
			notify <- true
		}
	case <-time.After(10 * time.Second):
		log.Critical("[Server] Failed to connect to RabbitMQ")
		os.Exit(1)
	}

	// Get a delivery channel from the connection
	log.Infof("[Server] Listening for deliveries on %s.#", s.ServiceName)
	deliveries, err := s.connection.Consume(s.ServiceName)
	if err != nil {
		log.Criticalf("[Server] [%s] Failed to consume from Rabbit", s.ServiceName)
	}

	// Handle deliveries
	for {
		select {
		case req, ok := <-deliveries:
			if !ok {
				log.Infof("[Server] Delivery channel closed, exiting")
				return
			}
			log.Tracef("[Server] Received new delivery: %#v", req)
			go s.handleRequest(req)
		case <-s.closeChan:
			// shut down server
			s.connection.Close()
			break
		}
	}

	log.Infof("Exiting")
	log.Flush()
}

func (s *AMQPServer) Close() {
	close(s.closeChan)
}

// handleRequest takes a delivery from AMQP, attempts to process it and return a response
func (s *AMQPServer) handleRequest(delivery amqp.Delivery) {

	// See if we have a matching endpoint for this request
	endpointName := strings.Replace(delivery.RoutingKey, fmt.Sprintf("%s.", s.ServiceName), "", -1)
	endpoint := s.endpointRegistry.Get(endpointName)
	if endpoint == nil {
		log.Errorf("[Server] Endpoint '%s' not found, cannot handle request", endpointName)
		s.respondWithError(delivery, errors.BadRequest("Endpoint not found"))
		return
	}

	// Handle the delivery
	req := NewAMQPRequest(&delivery)
	resp, err := endpoint.HandleRequest(req)
	if err != nil {
		s.respondWithError(delivery, err)
		return
	}
	if resp == nil {
		s.respondWithError(delivery, errors.BadResponse("Handler returned nil"))
		return
	}

	body, err := proto.Marshal(resp)
	if err != nil {
		log.Errorf("[Server] Failed to marshal response")
		s.respondWithError(delivery, errors.BadResponse("Failed to marshal response: "+err.Error()))
		return
	}

	// Build return delivery, and publish
	msg := amqp.Publishing{
		CorrelationId: delivery.CorrelationId,
		Timestamp:     time.Now().UTC(),
		Body:          body,
		Headers: map[string]interface{}{
			"Content-Encoding": "RESPONSE",
		},
	}
	s.connection.Publish("", delivery.ReplyTo, msg)
}

// respondWithError to a delivery, with the provided error
func (s *AMQPServer) respondWithError(delivery amqp.Delivery, err error) {

	// Ensure we have a service error in proto form
	// and marshal this for transmission
	svcErr := errors.Wrap(err)
	b, err := proto.Marshal(errors.Marshal(svcErr))
	if err != nil {
		// shit
	}

	// Construct a return message with an error
	msg := amqp.Publishing{
		CorrelationId: delivery.CorrelationId,
		Timestamp:     time.Now().UTC(),
		Body:          b,
		Headers: map[string]interface{}{
			"Content-Encoding": "ERROR",
		},
	}

	// Publish the error back to the client
	s.connection.Publish("", delivery.ReplyTo, msg)
}
<|endoftext|>