first commit
way too late.
This commit is contained in:
commit
d4f7dc57fc
53 files changed
+3518
No files matched your search
+30
@@ -0,0 +1,30 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
|
||||||
|
|
||||||
|
*.out
|
||||||
|
/static/js
|
||||||
|
/static/css/style.css
|
||||||
|
/doormanserver
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.3
|
||||||
|
- 1.4
|
||||||
|
- tip
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 didier amyot
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Doorman server [](https://travis-ci.org/didiercrunch/doormanserver)
|
||||||
|
|
||||||
|
|
||||||
|
This is the server of the doorman project. It is the centralized user interface
|
||||||
|
where you control the configuration of your A/B tests.
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/didiercrunch/doormanserver/doormen"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getDoormenIdsAsJson(id *doormen.DoormanId) string {
|
||||||
|
return fmt.Sprintf(`{"name": "%v", "id": "%v", "url": "/api/doormen/%v"}`, id.Name, id.Id.Hex(), id.Id.Hex())
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateDoorman(w http.ResponseWriter, request *http.Request) {
|
||||||
|
wdef := new(doormen.DoormanDefinition)
|
||||||
|
defer request.Body.Close()
|
||||||
|
decoder := json.NewDecoder(request.Body)
|
||||||
|
if err := decoder.Decode(wdef); err != nil {
|
||||||
|
w.WriteHeader(400)
|
||||||
|
fmt.Fprintln(w, "cannot create doorman definition \n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wdef.Id = bson.NewObjectId()
|
||||||
|
if err := wdef.Validate(); err != nil {
|
||||||
|
w.WriteHeader(400)
|
||||||
|
fmt.Fprintln(w, "New doorman is invalid \n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := CreateDoormanInDatabase(wdef); err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprintln(w, "Server error \n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go publisher.Emit(wdef.Id, wdef)
|
||||||
|
w.Header().Set("location", "/api/doormen/"+wdef.Id.Hex())
|
||||||
|
w.WriteHeader(201)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllDoormen(w http.ResponseWriter, request *http.Request) {
|
||||||
|
fmt.Fprint(w, `{"doormen" :[`)
|
||||||
|
doormenIds := conn.GetAllDoormen()
|
||||||
|
buff := <-doormenIds
|
||||||
|
if buff == nil {
|
||||||
|
fmt.Fprint(w, "]}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for doormanId := range doormenIds {
|
||||||
|
fmt.Fprint(w, getDoormenIdsAsJson(buff)+`,`)
|
||||||
|
buff = doormanId
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, getDoormenIdsAsJson(buff), "]}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDoormanIdFromRequest(w http.ResponseWriter, request *http.Request) bson.ObjectId {
|
||||||
|
id := mux.Vars(request)["id"]
|
||||||
|
if !bson.IsObjectIdHex(id) {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return bson.ObjectIdHex(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDoorman(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id := getDoormanIdFromRequest(w, r)
|
||||||
|
if id == "" {
|
||||||
|
Write404Error(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
doorman, err := conn.GetDoorman(id)
|
||||||
|
if err != nil {
|
||||||
|
Write500Error(w, err)
|
||||||
|
} else if doorman == nil {
|
||||||
|
Write404Error(w)
|
||||||
|
} else {
|
||||||
|
encoder := json.NewEncoder(w)
|
||||||
|
encoder.Encode(doorman)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateDoorman(w http.ResponseWriter, request *http.Request) {
|
||||||
|
id := getDoormanIdFromRequest(w, request)
|
||||||
|
if id == "" || !conn.ExistsId(id) {
|
||||||
|
Write404Error(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wdef := new(doormen.DoormanDefinition)
|
||||||
|
defer request.Body.Close()
|
||||||
|
decoder := json.NewDecoder(request.Body)
|
||||||
|
if err := decoder.Decode(wdef); err != nil {
|
||||||
|
Write400Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wdef.Id = id
|
||||||
|
|
||||||
|
if err := wdef.Validate(); err != nil {
|
||||||
|
Write400Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
originalDoorman, err := conn.GetDoorman(id)
|
||||||
|
if err != nil {
|
||||||
|
Write500Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := originalDoorman.CanBeUpdatedBy(wdef); err != nil {
|
||||||
|
Write400Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := UpdateDoormanInDatabase(wdef); err != nil {
|
||||||
|
Write500Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go publisher.Emit(wdef.Id, wdef)
|
||||||
|
w.WriteHeader(200)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type Api struct {
|
||||||
|
GetAllDoormen http.HandlerFunc
|
||||||
|
CreateDoorman http.HandlerFunc
|
||||||
|
GetDoorman http.HandlerFunc
|
||||||
|
UpdateDoorman http.HandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Api) ServeApi(router *mux.Router) {
|
||||||
|
router.Path("/doormen").Methods("GET").HandlerFunc(a.GetAllDoormen)
|
||||||
|
router.Path("/doormen").Methods("POST").HandlerFunc(a.CreateDoorman)
|
||||||
|
router.Path("/doormen/{id}").Methods("GET").HandlerFunc(a.GetDoorman)
|
||||||
|
router.Path("/doormen/{id}").Methods("PUT").HandlerFunc(a.UpdateDoorman)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveApi(router *mux.Router) {
|
||||||
|
api := new(Api)
|
||||||
|
api.CreateDoorman = CreateDoorman
|
||||||
|
api.GetAllDoormen = GetAllDoormen
|
||||||
|
api.GetDoorman = GetDoorman
|
||||||
|
api.UpdateDoorman = UpdateDoorman
|
||||||
|
api.ServeApi(router)
|
||||||
|
}
|
||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/didiercrunch/doormanserver/inmemoryconnector"
|
||||||
|
"github.com/didiercrunch/doormanserver/doormen"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
conn = inmemoryconnector.NewInMemoryDBConnector()
|
||||||
|
}
|
||||||
|
|
||||||
|
func OkHandler(w http.ResponseWriter, request *http.Request) {
|
||||||
|
fmt.Fprintf(w, "ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertBodyIs(t *testing.T, res *http.Response, expt string) {
|
||||||
|
if body, err := ioutil.ReadAll(res.Body); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else if s := string(body); s != expt {
|
||||||
|
t.Error("expected ", expt, " but received ", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPOSTToDoormen(t *testing.T) {
|
||||||
|
api := &Api{CreateDoorman: OkHandler}
|
||||||
|
r := mux.NewRouter()
|
||||||
|
api.ServeApi(r)
|
||||||
|
|
||||||
|
ts := httptest.NewServer(r)
|
||||||
|
defer ts.Close()
|
||||||
|
res, err := http.Post(ts.URL+"/doormen", "", bytes.NewBufferString("boby"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assertBodyIs(t, res, "ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGETToDoormen(t *testing.T) {
|
||||||
|
api := &Api{GetAllDoormen: OkHandler}
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
api.ServeApi(r)
|
||||||
|
|
||||||
|
ts := httptest.NewServer(r)
|
||||||
|
defer ts.Close()
|
||||||
|
res, err := http.Get(ts.URL + "/doormen")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assertBodyIs(t, res, "ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAllDoormenEmpty(t *testing.T) {
|
||||||
|
conn = inmemoryconnector.NewInMemoryDBConnector()
|
||||||
|
api := &Api{GetAllDoormen: GetAllDoormen}
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
api.ServeApi(r)
|
||||||
|
|
||||||
|
ts := httptest.NewServer(r)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
res, err := http.Get(ts.URL + "/doormen")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if body, err := ioutil.ReadAll(res.Body); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else if s := string(body); s != `{"doormen" :[]}` {
|
||||||
|
t.Error(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAllDoormen(t *testing.T) {
|
||||||
|
conn = inmemoryconnector.NewInMemoryDBConnector()
|
||||||
|
conn.Save(doormen.QuickNewDoormanDefinition("foo", 0.25, 0.75))
|
||||||
|
conn.Save(doormen.QuickNewDoormanDefinition("bar", 0.5, 0.5))
|
||||||
|
|
||||||
|
api := &Api{GetAllDoormen: GetAllDoormen}
|
||||||
|
r := mux.NewRouter()
|
||||||
|
api.ServeApi(r)
|
||||||
|
|
||||||
|
ts := httptest.NewServer(r)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
res, err := http.Get(ts.URL + "/doormen")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d := json.NewDecoder(res.Body)
|
||||||
|
m := make(map[string][]*doormen.DoormanDefinition)
|
||||||
|
|
||||||
|
if err := d.Decode(&m); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(m["doormen"]) != 2 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Write500Error(w http.ResponseWriter, err error) {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, "server error")
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Write404Error(w http.ResponseWriter) {
|
||||||
|
w.WriteHeader(404)
|
||||||
|
fmt.Fprint(w, "url not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Write400Error(w http.ResponseWriter, err error) {
|
||||||
|
w.WriteHeader(400)
|
||||||
|
fmt.Fprint(w, err.Error())
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWrite404Error(t *testing.T) {
|
||||||
|
rw := httptest.NewRecorder()
|
||||||
|
Write404Error(rw)
|
||||||
|
if rw.Code != 404 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrite500Error(t *testing.T) {
|
||||||
|
rw := httptest.NewRecorder()
|
||||||
|
Write500Error(rw, errors.New("blah"))
|
||||||
|
if rw.Code != 500 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if string(rw.Body.Bytes()) != "server error" {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrite400Error(t *testing.T) {
|
||||||
|
rw := httptest.NewRecorder()
|
||||||
|
Write400Error(rw, errors.New("blah"))
|
||||||
|
if rw.Code != 400 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if string(rw.Body.Bytes()) != "blah" {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/didiercrunch/doormanserver/inmemoryconnector"
|
||||||
|
"github.com/didiercrunch/doormanserver/mongoconnector"
|
||||||
|
"github.com/didiercrunch/doormanserver/shared"
|
||||||
|
"github.com/didiercrunch/doormanserver/doormen"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBConnector interface {
|
||||||
|
Save(wab *doormen.DoormanDefinition) error
|
||||||
|
Update(wab *doormen.DoormanDefinition) error
|
||||||
|
Exists(wab *doormen.DoormanDefinition) bool
|
||||||
|
ExistsId(id bson.ObjectId) bool
|
||||||
|
GetAllDoormen() <-chan *doormen.DoormanId
|
||||||
|
GetDoorman(id bson.ObjectId) (*doormen.DoormanDefinition, error)
|
||||||
|
DeleteDoorman(id bson.ObjectId) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var conn DBConnector
|
||||||
|
|
||||||
|
func initMongoDbConnector() {
|
||||||
|
var err error
|
||||||
|
if conn, err = mongoconnector.NewMongoDBConnector(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if params := shared.GetParams(); params.UseMongoDb() {
|
||||||
|
initMongoDbConnector()
|
||||||
|
log.Println("using mongodb as database")
|
||||||
|
} else {
|
||||||
|
conn = inmemoryconnector.NewInMemoryDBConnector()
|
||||||
|
log.Println("using inmemory as database")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateDoormanInDatabase(wab *doormen.DoormanDefinition) error {
|
||||||
|
if conn.Exists(wab) {
|
||||||
|
return errors.New("doormen already exists")
|
||||||
|
}
|
||||||
|
return conn.Save(wab)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateDoormanInDatabase(wab *doormen.DoormanDefinition) error {
|
||||||
|
return conn.Update(wab)
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/didiercrunch/doormanserver/inmemoryconnector"
|
||||||
|
"github.com/didiercrunch/doormanserver/doormen"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateDoorman(t *testing.T) {
|
||||||
|
wab := doormen.NewDoormanDefinition("some_name")
|
||||||
|
conn = inmemoryconnector.NewInMemoryDBConnector()
|
||||||
|
|
||||||
|
if err := CreateDoormanInDatabase(wab); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := CreateDoormanInDatabase(wab); err == nil {
|
||||||
|
t.Error("should have an error here")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package doormen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/didiercrunch/doorman/shared"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
const epsilon float64 = 0.0001
|
||||||
|
|
||||||
|
type DoormanValue struct {
|
||||||
|
Name string `json:"name" bson:"name"`
|
||||||
|
Probability float64 `json:"probability" bson:"probability"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DoormanId struct {
|
||||||
|
Id bson.ObjectId `json:"id,omitempty" bson:"_id"`
|
||||||
|
Name string `json:"name" bson:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wId *DoormanId) AsJson() string {
|
||||||
|
if wId.Id == "" {
|
||||||
|
return `{}`
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(`{"%v":"%v"}`, wId.Id.Hex(), wId.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DoormanDefinition struct {
|
||||||
|
Id bson.ObjectId `json:"id,omitempty" bson:"_id"`
|
||||||
|
Name string `json:"name" bson:"name"`
|
||||||
|
Values []*DoormanValue `json:"values" bson:"values"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDoormanDefinition(name string) *DoormanDefinition {
|
||||||
|
return &DoormanDefinition{Id: bson.NewObjectId(), Name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
// a way to quickly create doormen in tests. do not use in real code. will
|
||||||
|
// panic if a doorman is wrongly specify
|
||||||
|
func QuickNewDoormanDefinition(name string, probs ...float64) *DoormanDefinition {
|
||||||
|
ret := NewDoormanDefinition(name)
|
||||||
|
for i, prob := range probs {
|
||||||
|
ret.Values = append(ret.Values, &DoormanValue{"T" + strconv.Itoa(i), prob})
|
||||||
|
}
|
||||||
|
if err := ret.Validate(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dmd *DoormanDefinition) ValidateDoormanValueNames() error {
|
||||||
|
m := make(map[string]bool)
|
||||||
|
for _, w := range dmd.Values {
|
||||||
|
if name := w.Name; name == "" {
|
||||||
|
return errors.New("doorman value cannot be empty")
|
||||||
|
} else {
|
||||||
|
m[name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(m) != len(dmd.Values) {
|
||||||
|
return errors.New("doorman value names must be unique within a doorman")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dmd *DoormanDefinition) ValidateDoormanValueProbabilities() error {
|
||||||
|
var sum float64 = 0
|
||||||
|
for _, w := range dmd.Values {
|
||||||
|
if p := w.Probability; p < 0-epsilon || p > 1+epsilon {
|
||||||
|
return errors.New(fmt.Sprintf("doorman value %v is out of range", p))
|
||||||
|
} else {
|
||||||
|
sum += p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sum < 0-epsilon || sum > 1+epsilon {
|
||||||
|
return errors.New("the sum of the probability must be 1.0")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dmd *DoormanDefinition) Validate() (err error) {
|
||||||
|
switch {
|
||||||
|
case dmd.Name == "":
|
||||||
|
err = errors.New("name cannot be empty")
|
||||||
|
case len(dmd.Values) < 2:
|
||||||
|
err = errors.New("need at least 2 values")
|
||||||
|
case dmd.ValidateDoormanValueNames() != nil:
|
||||||
|
err = dmd.ValidateDoormanValueNames()
|
||||||
|
case dmd.ValidateDoormanValueProbabilities() != nil:
|
||||||
|
err = dmd.ValidateDoormanValueProbabilities()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dmd *DoormanDefinition) CanBeUpdatedBy(dmd2 *DoormanDefinition) error {
|
||||||
|
if dmd.Name != dmd2.Name {
|
||||||
|
return errors.New("cannot change doorman name")
|
||||||
|
}
|
||||||
|
if len(dmd.Values) != len(dmd2.Values) {
|
||||||
|
return errors.New("cannot change the number of cases in doorman")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dmd *DoormanDefinition) AsDoormanUpdatePayload() ([]byte, error) {
|
||||||
|
wu := &shared.DoormanUpdater{Id: dmd.Id.Hex()}
|
||||||
|
wu.Timestamp = time.Now().Unix()
|
||||||
|
wu.Probabilities = make([]float64, len(dmd.Values))
|
||||||
|
for i, value := range dmd.Values {
|
||||||
|
wu.Probabilities[i] = value.Probability
|
||||||
|
}
|
||||||
|
return json.Marshal(wu)
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package doormen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/didiercrunch/doorman/shared"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewDoormanDefinition(t *testing.T) {
|
||||||
|
dmd := NewDoormanDefinition("some_name")
|
||||||
|
if dmd.Id == "" {
|
||||||
|
t.Error("need id at all time")
|
||||||
|
}
|
||||||
|
if dmd.Name != "some_name" {
|
||||||
|
t.Error("bad name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate(t *testing.T) {
|
||||||
|
dmd := &DoormanDefinition{Name: ""}
|
||||||
|
if err := dmd.Validate(); err.Error() != "name cannot be empty" {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
dmd = &DoormanDefinition{Name: "foo"}
|
||||||
|
if err := dmd.Validate(); err.Error() != "need at least 2 values" {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{nil}}
|
||||||
|
if err := dmd.Validate(); err.Error() != "need at least 2 values" {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0}, &DoormanValue{"foo", 0}}}
|
||||||
|
if err := dmd.Validate(); err.Error() != "doorman value names must be unique within a doorman" {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0}, &DoormanValue{"", 0}}}
|
||||||
|
if err := dmd.Validate(); err.Error() != "doorman value cannot be empty" {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", -0.25}, &DoormanValue{"bar", 0.5}}}
|
||||||
|
if err := dmd.Validate(); err.Error() != "doorman value -0.25 is out of range" {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 2.5}, &DoormanValue{"bar", 0.5}}}
|
||||||
|
if err := dmd.Validate(); err.Error() != "doorman value 2.5 is out of range" {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0.75}, &DoormanValue{"bar", 0.5}}}
|
||||||
|
if err := dmd.Validate(); err.Error() != "the sum of the probability must be 1.0" {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAsJson(t *testing.T) {
|
||||||
|
id := &DoormanId{}
|
||||||
|
if `{}` != id.AsJson() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
id.Name = "bob"
|
||||||
|
if `{}` != id.AsJson() {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
id.Id = bson.ObjectIdHex("54af464af6fb5e20c4000003")
|
||||||
|
if `{"54af464af6fb5e20c4000003":"bob"}` != id.AsJson() {
|
||||||
|
t.Error(id.AsJson())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAsDoormanUpdatePayload(t *testing.T) {
|
||||||
|
wd := &DoormanDefinition{}
|
||||||
|
wd.Id = bson.NewObjectId()
|
||||||
|
wd.Values = []*DoormanValue{&DoormanValue{"C", 0.25}, &DoormanValue{"T1", 0.75}}
|
||||||
|
data, err := wd.AsDoormanUpdatePayload()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wu := &shared.DoormanUpdater{}
|
||||||
|
if err := json.Unmarshal(data, wu); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if wd.Id.Hex() != wu.Id {
|
||||||
|
t.Error("bad id", wu.Id)
|
||||||
|
}
|
||||||
|
if len(wu.Probabilities) != 2 {
|
||||||
|
t.Error("bad probability length")
|
||||||
|
} else if math.Abs(wu.Probabilities[0]-0.25) > epsilon {
|
||||||
|
t.Error("bad first probability")
|
||||||
|
} else if math.Abs(wu.Probabilities[1]-0.75) > epsilon {
|
||||||
|
t.Error("bad second probability")
|
||||||
|
} else if wu.Timestamp == 0 {
|
||||||
|
t.Error("no time specify")
|
||||||
|
} else if wu.Timestamp < 1421537442 {
|
||||||
|
t.Error("timestamp looks weird")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuickNewDoormanDefinition(t *testing.T) {
|
||||||
|
dmd := QuickNewDoormanDefinition("foo", 0.75, 0.25)
|
||||||
|
if dmd.Name != "foo" {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dmd.Values) != 2 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if dmd.Values[0].Name != "T0" || dmd.Values[0].Probability != 0.75 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if dmd.Values[1].Name != "T1" || dmd.Values[1].Probability != 0.25 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package inmemoryconnector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/didiercrunch/doormanserver/doormen"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createMockDoorman(name string) *doormen.DoormanDefinition {
|
||||||
|
return doormen.QuickNewDoormanDefinition(name, 0.5, 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInMemoryDBConnector(t *testing.T) {
|
||||||
|
c := NewInMemoryDBConnector()
|
||||||
|
if len(c.memory) != 0 {
|
||||||
|
t.Error("length at this point should be 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSave(t *testing.T) {
|
||||||
|
c := NewInMemoryDBConnector()
|
||||||
|
if err := c.Save(nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if len(c.memory) != 1 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteDoorman(t *testing.T) {
|
||||||
|
c := NewInMemoryDBConnector()
|
||||||
|
c.Save(createMockDoorman("a"))
|
||||||
|
c.Save(createMockDoorman("b"))
|
||||||
|
c.Save(createMockDoorman("c"))
|
||||||
|
|
||||||
|
if err := c.DeleteDoorman(c.memory[1].Id); err != nil {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if c.memory[0].Name != "a" || c.memory[1].Name != "c" {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.DeleteDoorman(c.memory[1].Id); err != nil {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if c.memory[0].Name != "a" {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.DeleteDoorman(c.memory[0].Id); err != nil {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if len(c.memory) != 0 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAllDoormen(t *testing.T) {
|
||||||
|
c := NewInMemoryDBConnector()
|
||||||
|
c.Save(createMockDoorman("a"))
|
||||||
|
c.Save(createMockDoorman("b"))
|
||||||
|
c.Save(createMockDoorman("c"))
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
expt := []string{"a", "b", "c"}
|
||||||
|
for w := range c.GetAllDoormen() {
|
||||||
|
if w.Name != expt[i] {
|
||||||
|
t.Error(i)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDoorman(t *testing.T) {
|
||||||
|
c := NewInMemoryDBConnector()
|
||||||
|
wab := createMockDoorman("a")
|
||||||
|
c.Save(wab)
|
||||||
|
|
||||||
|
if w, err := c.GetDoorman(wab.Id); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else if w.Name != "a" {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if w, err := c.GetDoorman(createMockDoorman("b").Id); err != nil {
|
||||||
|
t.Error("missing doorman is not an error")
|
||||||
|
} else if w != nil {
|
||||||
|
t.Error("should be nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExistsId(t *testing.T) {
|
||||||
|
c := NewInMemoryDBConnector()
|
||||||
|
w := createMockDoorman("a")
|
||||||
|
c.Save(w)
|
||||||
|
|
||||||
|
if !c.ExistsId(w.Id) {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if c.ExistsId(bson.NewObjectId()) {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package inmemoryconnector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/didiercrunch/doormanserver/doormen"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InMemoryDBConnector struct {
|
||||||
|
memory []*doormen.DoormanDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInMemoryDBConnector() *InMemoryDBConnector {
|
||||||
|
return &InMemoryDBConnector{make([]*doormen.DoormanDefinition, 0)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *InMemoryDBConnector) Save(dmd *doormen.DoormanDefinition) error {
|
||||||
|
c.memory = append(c.memory, dmd)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *InMemoryDBConnector) Exists(dmd *doormen.DoormanDefinition) bool {
|
||||||
|
for _, w := range c.memory {
|
||||||
|
if w.Name == dmd.Name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *InMemoryDBConnector) ExistsId(id bson.ObjectId) bool {
|
||||||
|
for _, w := range c.memory {
|
||||||
|
if w.Id == id {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *InMemoryDBConnector) GetAllDoormen() <-chan *doormen.DoormanId {
|
||||||
|
ch := make(chan *doormen.DoormanId)
|
||||||
|
go func() {
|
||||||
|
for _, s := range c.memory {
|
||||||
|
ch <- &doormen.DoormanId{Name: s.Name, Id: s.Id}
|
||||||
|
}
|
||||||
|
close(ch)
|
||||||
|
}()
|
||||||
|
return ch
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *InMemoryDBConnector) GetDoorman(id bson.ObjectId) (*doormen.DoormanDefinition, error) {
|
||||||
|
for _, doorman := range c.memory {
|
||||||
|
if doorman.Id == id {
|
||||||
|
return doorman, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *InMemoryDBConnector) DeleteDoorman(id bson.ObjectId) error {
|
||||||
|
toRemove := -1
|
||||||
|
for i, doorman := range c.memory {
|
||||||
|
if doorman.Id == id {
|
||||||
|
toRemove = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.memory = append(c.memory[0:toRemove], c.memory[toRemove+1:len(c.memory)]...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *InMemoryDBConnector) Update(dmd *doormen.DoormanDefinition) error {
|
||||||
|
for i, w := range c.memory {
|
||||||
|
if w.Id == dmd.Id {
|
||||||
|
c.memory[i] = dmd
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("nothing to update")
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package logpublisher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/didiercrunch/doormanserver/shared"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogPublisher struct {
|
||||||
|
Printer func(v ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pub *LogPublisher) Emit(doormanId bson.ObjectId, doorman shared.AsDoormanUpdatePayloader) error {
|
||||||
|
if data, err := doorman.AsDoormanUpdatePayload(); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
pub.Printer(doormanId.Hex(), " : ", string(data))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pub *LogPublisher) Init() error {
|
||||||
|
pub.Printer = log.Println
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package logpublisher
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestLogPublisher(t *testing.T) {}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package mongoconnector
|
||||||
|
|
||||||
|
//var mongoClient int
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/didiercrunch/doormanserver/shared"
|
||||||
|
mgo "gopkg.in/mgo.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mongoClient *mgo.Session
|
||||||
|
var DB *mgo.Database
|
||||||
|
|
||||||
|
func initMongo() error {
|
||||||
|
if mongoClient != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
mongoClient, err = mgo.Dial(shared.GetParams().Mongo.Url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
DB = mongoClient.DB(shared.GetParams().Mongo.DatabaseName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package mongoconnector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/didiercrunch/doormanserver/shared"
|
||||||
|
"github.com/didiercrunch/doormanserver/doormen"
|
||||||
|
mgo "gopkg.in/mgo.v2"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MongoDBConnector struct {
|
||||||
|
Collection *mgo.Collection
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMongoDBConnector() (*MongoDBConnector, error) {
|
||||||
|
if err := initMongo(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c := new(MongoDBConnector)
|
||||||
|
params := shared.GetParams()
|
||||||
|
c.Collection = DB.C(params.Mongo.DoormanDefinitionCollection)
|
||||||
|
err := c.Collection.EnsureIndex(mgo.Index{Key: []string{"name"}, Unique: true})
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MongoDBConnector) Exists(wab *doormen.DoormanDefinition) bool {
|
||||||
|
err := c.Collection.Find(bson.M{"name": wab.Name}).One(&struct{}{})
|
||||||
|
return err != mgo.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MongoDBConnector) ExistsId(id bson.ObjectId) bool {
|
||||||
|
err := c.Collection.FindId(id).One(&struct{}{})
|
||||||
|
return err != mgo.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MongoDBConnector) Save(wab *doormen.DoormanDefinition) error {
|
||||||
|
return c.Collection.Insert(wab)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MongoDBConnector) GetAllDoormen() <-chan *doormen.DoormanId {
|
||||||
|
ch := make(chan *doormen.DoormanId)
|
||||||
|
go func() {
|
||||||
|
iter := c.Collection.Find(nil).Iter()
|
||||||
|
|
||||||
|
for res := new(doormen.DoormanId); iter.Next(res); res = new(doormen.DoormanId) {
|
||||||
|
ch <- res
|
||||||
|
}
|
||||||
|
if err := iter.Err(); err != nil {
|
||||||
|
ch <- &doormen.DoormanId{Id: "", Name: err.Error()}
|
||||||
|
}
|
||||||
|
close(ch)
|
||||||
|
}()
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MongoDBConnector) GetDoorman(id bson.ObjectId) (*doormen.DoormanDefinition, error) {
|
||||||
|
doorman := new(doormen.DoormanDefinition)
|
||||||
|
if err := c.Collection.FindId(id).One(doorman); err == mgo.ErrNotFound {
|
||||||
|
return nil, nil
|
||||||
|
} else {
|
||||||
|
return doorman, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MongoDBConnector) DeleteDoorman(id bson.ObjectId) error {
|
||||||
|
return c.Collection.RemoveId(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MongoDBConnector) Update(wab *doormen.DoormanDefinition) error {
|
||||||
|
return c.Collection.Update(&bson.M{"_id": wab.Id}, &bson.M{"$set": wab})
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package nanomsgpublisher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/didiercrunch/doormanserver/shared"
|
||||||
|
"github.com/gdamore/mangos"
|
||||||
|
"github.com/gdamore/mangos/protocol/pub"
|
||||||
|
"github.com/gdamore/mangos/transport/ipc"
|
||||||
|
"github.com/gdamore/mangos/transport/tcp"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NanoMsgPublisher struct {
|
||||||
|
Url string
|
||||||
|
sock mangos.Socket
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *NanoMsgPublisher) Init() error {
|
||||||
|
var err error
|
||||||
|
if p.sock, err = pub.NewSocket(); err != nil {
|
||||||
|
return errors.New("can't listen on pub socket:" + err.Error())
|
||||||
|
}
|
||||||
|
p.sock.AddTransport(ipc.NewTransport())
|
||||||
|
p.sock.AddTransport(tcp.NewTransport())
|
||||||
|
if err = p.sock.Listen(shared.GetParams().NanoMsg.NanoMsgUrl); err != nil {
|
||||||
|
return errors.New("can't listen on pub socket: %s" + err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *NanoMsgPublisher) Emit(doormanId bson.ObjectId, doorman shared.AsDoormanUpdatePayloader) error {
|
||||||
|
log.Println("Emit for ", doormanId.Hex())
|
||||||
|
if d, err := doorman.AsDoormanUpdatePayload(); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
return p.sock.Send(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package nsqpublisher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bitly/go-nsq"
|
||||||
|
"github.com/didiercrunch/doormanserver/shared"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NsqPublisher struct {
|
||||||
|
Config *nsq.Config
|
||||||
|
Producer *nsq.Producer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pub *NsqPublisher) InitConfig() {
|
||||||
|
pub.Config = nsq.NewConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pub *NsqPublisher) Init() error {
|
||||||
|
var err error
|
||||||
|
pub.InitConfig()
|
||||||
|
pub.Producer, err = nsq.NewProducer(shared.GetParams().NSQ.NSQLookupdUrl, pub.Config)
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pub *NsqPublisher) Stop() error {
|
||||||
|
pub.Producer.Stop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pub *NsqPublisher) Emit(doormanId bson.ObjectId, doorman shared.AsDoormanUpdatePayloader) error {
|
||||||
|
topic := doormanId.Hex()
|
||||||
|
if data, err := doorman.AsDoormanUpdatePayload(); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
return pub.Producer.Publish(topic, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
# This is union web site params' file. The file is in yaml format. The file
|
||||||
|
# should be auto-documented
|
||||||
|
|
||||||
|
|
||||||
|
# the port where the union web site will run
|
||||||
|
port: 1999
|
||||||
|
|
||||||
|
# the host name. 0.0.0.0 to share the service with everyone on the network.
|
||||||
|
# localhost for running on localhost without the possibility to share the
|
||||||
|
# service
|
||||||
|
host: 0.0.0.0 #localhost
|
||||||
|
|
||||||
|
# determine which message queue to use
|
||||||
|
message_queue: nanomsg # nanomsg # nsq
|
||||||
|
|
||||||
|
# determine which database to use
|
||||||
|
database: in_memory # mongo
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
# all the parameters relative to nsq message-queue. To use nsq as message
|
||||||
|
# queue you need to speficy "nsq" in the "message_queue" key at top level.
|
||||||
|
nsq:
|
||||||
|
nsq_lookupd_url: 127.0.0.1:4150
|
||||||
|
|
||||||
|
|
||||||
|
# all the parameters related to mongodb. To use mongodb as your database you
|
||||||
|
# need to specify "mongodb" in the "database" key at top level
|
||||||
|
mongo:
|
||||||
|
# the url where to get mongodb
|
||||||
|
url: localhost:27017
|
||||||
|
|
||||||
|
# the name of the database to use
|
||||||
|
database_name: doorman
|
||||||
|
|
||||||
|
# the name of the collection where all the street can be found
|
||||||
|
doorman_definition_collection: doorman_definitions
|
||||||
|
|
||||||
|
# all the parameters relative to nanomsg message-queue
|
||||||
|
nanomsg:
|
||||||
|
nanomsg_url: tcp://127.0.0.1:40899
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/didiercrunch/doormanserver/logpublisher"
|
||||||
|
"github.com/didiercrunch/doormanserver/nanomsgpublisher"
|
||||||
|
"github.com/didiercrunch/doormanserver/nsqpublisher"
|
||||||
|
"github.com/didiercrunch/doormanserver/shared"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Publisher interface {
|
||||||
|
Emit(doormanId bson.ObjectId, doorman shared.AsDoormanUpdatePayloader) error
|
||||||
|
Init() error
|
||||||
|
}
|
||||||
|
|
||||||
|
var publisher Publisher
|
||||||
|
|
||||||
|
func initNSQPublisher() {
|
||||||
|
publisher = new(nsqpublisher.NsqPublisher)
|
||||||
|
if err := publisher.Init(); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initNanoMsgPublisher() {
|
||||||
|
publisher = new(nanomsgpublisher.NanoMsgPublisher)
|
||||||
|
if err := publisher.Init(); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initPublisher(params *shared.Params) {
|
||||||
|
if params.UseNSQ() {
|
||||||
|
initNSQPublisher()
|
||||||
|
log.Println("using nsq as message queue")
|
||||||
|
|
||||||
|
} else if params.UseNanomsg() {
|
||||||
|
initNanoMsgPublisher()
|
||||||
|
log.Println("using nanomsg as message queue")
|
||||||
|
} else {
|
||||||
|
publisher = &logpublisher.LogPublisher{log.Println}
|
||||||
|
log.Println("using stdout as message queue")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func init() {
|
||||||
|
initPublisher(shared.GetParams())
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestInitPublisher(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/didiercrunch/doormanserver/shared"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createMuxRouter() http.Handler {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
serveApi(r.PathPrefix("/api").Subrouter())
|
||||||
|
serveStatic(r.PathPrefix("/").Subrouter())
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAddressToServe() string {
|
||||||
|
params := shared.GetParams()
|
||||||
|
return params.Host + ":" + strconv.Itoa(params.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := createMuxRouter()
|
||||||
|
http.Handle("/", r)
|
||||||
|
address := GetAddressToServe()
|
||||||
|
fmt.Println("server running at", address)
|
||||||
|
if err := http.ListenAndServe(address, createMuxRouter()); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package shared
|
||||||
|
|
||||||
|
type AsDoormanUpdatePayloader interface {
|
||||||
|
AsDoormanUpdatePayload() ([]byte, error)
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package shared
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
goyaml "gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var PARAM_FILE string = "params.yml"
|
||||||
|
|
||||||
|
var params *Params
|
||||||
|
|
||||||
|
type MongoParams struct {
|
||||||
|
Url string `yaml:"url,omitempty"`
|
||||||
|
DatabaseName string `yaml:"database_name,omitempty"`
|
||||||
|
DoormanDefinitionCollection string `yaml:"doorman_definition_collection,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NSQParams struct {
|
||||||
|
NSQLookupdUrl string `yaml:"nsq_lookupd_url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NanoMsgParams struct {
|
||||||
|
NanoMsgUrl string `yaml:"nanomsg_url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Params struct {
|
||||||
|
Port int `yaml:"port,omitempty"`
|
||||||
|
Host string `yaml:"host,omitempty"`
|
||||||
|
Mongo *MongoParams `yaml:"mongo,omitempty"`
|
||||||
|
NSQ *NSQParams `yaml:"nsq,omitempty"`
|
||||||
|
NanoMsg *NanoMsgParams `yaml:"nanomsg,omitempty"`
|
||||||
|
|
||||||
|
Database string `yaml:"database,omitempty"`
|
||||||
|
MessageQueue string `yaml:"message_queue,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Params) UseMongoDb() bool {
|
||||||
|
db := strings.ToLower(p.Database)
|
||||||
|
return db == "mongo" || db == "mongodb"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Params) UseNSQ() bool {
|
||||||
|
m := strings.ToLower(p.MessageQueue)
|
||||||
|
return m == "nsq"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Params) UseNanomsg() bool {
|
||||||
|
m := strings.ToLower(p.MessageQueue)
|
||||||
|
return m == "nanomsg"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Params) InsertDefaultValues() *Params {
|
||||||
|
if p.Host == "" {
|
||||||
|
p.Host = "localhost"
|
||||||
|
}
|
||||||
|
if p.Port == 0 {
|
||||||
|
p.Port = 1999
|
||||||
|
}
|
||||||
|
if p.Mongo == nil {
|
||||||
|
p.Mongo = new(MongoParams)
|
||||||
|
}
|
||||||
|
if p.NSQ == nil {
|
||||||
|
p.NSQ = new(NSQParams)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadFromYamlFile(fileName string) (*Params, error) {
|
||||||
|
if !Exists(fileName) {
|
||||||
|
return new(Params).InsertDefaultValues(), nil
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadFile(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return LoadFromYamlData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadFromYamlData(data []byte) (*Params, error) {
|
||||||
|
var err error
|
||||||
|
p := new(Params)
|
||||||
|
if err = goyaml.Unmarshal(data, p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p.InsertDefaultValues(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetParams() *Params {
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
if params, err = LoadFromYamlFile(PARAM_FILE); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
package shared
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const yaml_params = `
|
||||||
|
---
|
||||||
|
port: 9999
|
||||||
|
host: 0.0.0.0 #localhost
|
||||||
|
# all the params relative to mongodb
|
||||||
|
mongo:
|
||||||
|
# the url where to get mongodb
|
||||||
|
url: localhost:1234
|
||||||
|
|
||||||
|
# the name of the database to use
|
||||||
|
database_name: doorman
|
||||||
|
|
||||||
|
# the name of the collection where all the street can be found
|
||||||
|
doorman_definition_collection: doorman_definitions
|
||||||
|
|
||||||
|
# all the parameters relative to nsq message-queue
|
||||||
|
nsq:
|
||||||
|
# the url where to find nsq lookupd url
|
||||||
|
nsq_lookupd_url: 127.0.0.1:4161
|
||||||
|
|
||||||
|
# all the parameters relative to nanomsg message-queue
|
||||||
|
nanomsg:
|
||||||
|
nanomsg_url: ipc:///tmp/doorman.ipc
|
||||||
|
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestLoadFromYamlData(t *testing.T) {
|
||||||
|
p, err := LoadFromYamlData([]byte(yaml_params))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.Port != 9999 {
|
||||||
|
t.Error("port")
|
||||||
|
}
|
||||||
|
if p.Host != "0.0.0.0" {
|
||||||
|
t.Error("host")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMongoParams(t *testing.T) {
|
||||||
|
p, err := LoadFromYamlData([]byte(yaml_params))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.Mongo.Url != "localhost:1234" {
|
||||||
|
t.Error("mongo.url")
|
||||||
|
}
|
||||||
|
if p.Mongo.DatabaseName != "doorman" {
|
||||||
|
t.Error("mongo.DatabaseName")
|
||||||
|
}
|
||||||
|
if p.Mongo.DoormanDefinitionCollection != "doorman_definitions" {
|
||||||
|
t.Error("Mongo.DoormanDefinitionCollection", p.Mongo.DoormanDefinitionCollection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNSQParams(t *testing.T) {
|
||||||
|
p, err := LoadFromYamlData([]byte(yaml_params))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.NSQ.NSQLookupdUrl != "127.0.0.1:4161" {
|
||||||
|
t.Error("Nsq.NSQLookupdUrl", p.NSQ.NSQLookupdUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNanoMsgParams(t *testing.T) {
|
||||||
|
p, err := LoadFromYamlData([]byte(yaml_params))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.NanoMsg.NanoMsgUrl != "ipc:///tmp/doorman.ipc" {
|
||||||
|
t.Error("NanoMsgParams.NanoMsgUrl", p.NanoMsg.NanoMsgUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMinimalParams(t *testing.T) {
|
||||||
|
params := "---\n"
|
||||||
|
p, err := LoadFromYamlData([]byte(params))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if p.Mongo.Url != "" {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if p.Port != 1999 {
|
||||||
|
t.Error("bad default port", p.Port)
|
||||||
|
}
|
||||||
|
if p.Host != "localhost" {
|
||||||
|
t.Error("bad default host")
|
||||||
|
}
|
||||||
|
if p.MessageQueue != "" {
|
||||||
|
t.Error("message queue")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Database != "" {
|
||||||
|
t.Error("database")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUseMongoDb(t *testing.T) {
|
||||||
|
p := &Params{}
|
||||||
|
if p.Database = ""; p.UseMongoDb() {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if p.Database = "mOnGo"; !p.UseMongoDb() {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if p.Database = "monGoDb"; !p.UseMongoDb() {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUseNSQ(t *testing.T) {
|
||||||
|
p := &Params{}
|
||||||
|
if p.MessageQueue = ""; p.UseNSQ() {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if p.MessageQueue = "nSq"; !p.UseNSQ() {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUseNanoMsg(t *testing.T) {
|
||||||
|
p := &Params{}
|
||||||
|
if p.MessageQueue = ""; p.UseNanomsg() {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if p.MessageQueue = "nAnOmSg"; !p.UseNanomsg() {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package shared
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func Exists(name string) bool {
|
||||||
|
if _, err := os.Stat(name); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
logs/*
|
||||||
|
!.gitkeep
|
||||||
|
node_modules/
|
||||||
|
bower_components/
|
||||||
|
tmp
|
||||||
|
.DS_Store
|
||||||
|
.idea
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "angular-requirejs-seed",
|
||||||
|
"description": "AngularJS seed project modified for requireJS support.",
|
||||||
|
"version": "0.2.0",
|
||||||
|
"homepage": "https://github.com/tnajdek/angular-requirejs-seed",
|
||||||
|
"license": "MIT",
|
||||||
|
"ignore": [
|
||||||
|
"**/.*",
|
||||||
|
"node_modules",
|
||||||
|
"bower_components",
|
||||||
|
"test",
|
||||||
|
"tests"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"angular": "~1.2.1",
|
||||||
|
"angular-route": "~1.2.1",
|
||||||
|
"angular-scenario": "~1.2.1",
|
||||||
|
"angular-mocks": "~1.2.1",
|
||||||
|
"requirejs": "~2.1.9",
|
||||||
|
"requirejs-text": "~2.0.10",
|
||||||
|
"foundation": "zurb/bower-foundation",
|
||||||
|
"html5-boilerplate": "~4.3.0",
|
||||||
|
"angularAMD": "~0.2.1",
|
||||||
|
"angular-foundation": "~0.5.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
define [
|
||||||
|
"angularAMD"
|
||||||
|
"angular-route"
|
||||||
|
"foundation"
|
||||||
|
], (angularAMD) ->
|
||||||
|
app = angular.module("webapp", ["ngRoute", "mm.foundation"])
|
||||||
|
app.config ($routeProvider) ->
|
||||||
|
$routeProvider.when("/", angularAMD.route(
|
||||||
|
templateUrl: "partials/home.html"
|
||||||
|
controller: "HomeCtrl"
|
||||||
|
controllerUrl: "controllers/homeCtrl"
|
||||||
|
)).when("/apispec", angularAMD.route(
|
||||||
|
templateUrl: "partials/apispec.html"
|
||||||
|
controller: "ApispecCtrl"
|
||||||
|
controllerUrl: "controllers/apispecCtrl"
|
||||||
|
)).when("/newdoorman", angularAMD.route(
|
||||||
|
templateUrl: "partials/newdoorman.html"
|
||||||
|
controller: "NewDoormanCtrl"
|
||||||
|
controllerUrl: "controllers/newdoormanCtrl"
|
||||||
|
)).when("/doormen", angularAMD.route(
|
||||||
|
templateUrl: "partials/doormen.html"
|
||||||
|
controller: "DoormenCtrl"
|
||||||
|
controllerUrl: "controllers/doormenCtrl"
|
||||||
|
)).when("/doormen/error/:error/:id", angularAMD.route(
|
||||||
|
templateUrl: "partials/doormanError.html"
|
||||||
|
controller: "DoormanErrorCtrl"
|
||||||
|
controllerUrl: "controllers/doormanErrorCtrl"
|
||||||
|
)).when("/doormen/:id", angularAMD.route(
|
||||||
|
templateUrl: "partials/doorman.html"
|
||||||
|
controller: "DoormanCtrl"
|
||||||
|
controllerUrl: "controllers/doormanCtrl"
|
||||||
|
)).otherwise redirectTo: "/"
|
||||||
|
return
|
||||||
|
|
||||||
|
angularAMD.bootstrap(app)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
define ["app", "angularAMD"], (app, angularAMD) ->
|
||||||
|
app.controller "ApispecCtrl", ($scope) ->
|
||||||
|
$scope.methods =
|
||||||
|
get: "GET"
|
||||||
|
post: "POST"
|
||||||
|
|
||||||
|
angularAMD.directive "wabMethod", () ->
|
||||||
|
directive =
|
||||||
|
restrict: 'E',
|
||||||
|
scope:
|
||||||
|
method: '@'
|
||||||
|
link: (scope, element) ->
|
||||||
|
scope.methods =
|
||||||
|
get: "GET"
|
||||||
|
put: "PUT"
|
||||||
|
delete: "DELETE"
|
||||||
|
post: "POST"
|
||||||
|
|
||||||
|
template: '<code style="width:5em; display: block; text-align: center;">{{ methods[method] }}</code>'
|
||||||
|
return directive
|
||||||
|
|
||||||
|
angularAMD.directive "wabApi", () ->
|
||||||
|
directive =
|
||||||
|
restrict: 'E',
|
||||||
|
scope:
|
||||||
|
endpoint: '@'
|
||||||
|
template: '<a>/api{{ endpoint }}</a>'
|
||||||
|
return directive
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
define ["app"], (app) ->
|
||||||
|
app.controller "DoormanCtrl", ["$scope", "$http","$routeParams","$location", ($scope, $http, $routeParams, $location) ->
|
||||||
|
$scope.doorman = {values: []}
|
||||||
|
$scope.apiurl = "/api/doormen/#{$routeParams.id}"
|
||||||
|
$http.get($scope.apiurl).then((res) ->
|
||||||
|
$scope.doorman = res.data
|
||||||
|
).catch((res) ->
|
||||||
|
if res.status == 404
|
||||||
|
$location.path("/doormen/error/#{res.status}/#{$routeParams.id}")
|
||||||
|
)
|
||||||
|
|
||||||
|
$scope.deleteDoorman = () ->
|
||||||
|
console.log("not yet implemented")
|
||||||
|
|
||||||
|
$scope.validate = ()->
|
||||||
|
ret = 0
|
||||||
|
for val in $scope.doorman.values
|
||||||
|
ret += val.probability
|
||||||
|
return Math.abs(ret - 1.0) < 0.000001
|
||||||
|
|
||||||
|
$scope.updateDoorman = () ->
|
||||||
|
if !$scope.validate()
|
||||||
|
return
|
||||||
|
$http.put($scope.apiurl, $scope.doorman).then((ret)->
|
||||||
|
console.log "update"
|
||||||
|
).catch((res) ->
|
||||||
|
d = 9
|
||||||
|
)
|
||||||
|
]
|
||||||
|
return
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
define ["app"], (app) ->
|
||||||
|
app.controller "DoormanErrorCtrl", ["$scope","$routeParams", ($scope, $routeParams) ->
|
||||||
|
$scope.id = $routeParams.id
|
||||||
|
$scope.error = Number($routeParams.error)
|
||||||
|
]
|
||||||
|
return
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
define ["app"], (app) ->
|
||||||
|
app.controller "DoormenCtrl",[ "$scope", "$http", ($scope, $http) ->
|
||||||
|
$scope.doormen = []
|
||||||
|
$http.get("/api/doormen").then (res) ->
|
||||||
|
$scope.doormen = res.data.doormen
|
||||||
|
return
|
||||||
|
]
|
||||||
|
|
||||||
|
return
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
define ["app"], (app) ->
|
||||||
|
app.controller "HomeCtrl", ($scope) ->
|
||||||
|
$scope.message = "message from home ctrl"
|
||||||
|
return
|
||||||
|
|
||||||
|
return
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
epsilon = 0.0001
|
||||||
|
|
||||||
|
define ["app"], (app) ->
|
||||||
|
app.controller "NewDoormanCtrl", ["$scope", "$http", "$location", ($scope, $http, $location) ->
|
||||||
|
$scope.error = ""
|
||||||
|
$scope.value = {}
|
||||||
|
$scope.payload =
|
||||||
|
name: ""
|
||||||
|
values: []
|
||||||
|
|
||||||
|
$scope.isValidValue = () ->
|
||||||
|
return $scope.value.name and $scope.value.probability
|
||||||
|
|
||||||
|
$scope.addNewValue = () ->
|
||||||
|
if !$scope.isValidValue()
|
||||||
|
return
|
||||||
|
$scope.payload.values.push($scope.value)
|
||||||
|
$scope.value = {name: "", probability: 0}
|
||||||
|
|
||||||
|
$scope.removeValue = (idx) ->
|
||||||
|
$scope.payload.values.splice(idx, 1);
|
||||||
|
|
||||||
|
$scope.getProbabilityRemaining = () ->
|
||||||
|
ret = 0
|
||||||
|
for v in $scope.payload.values
|
||||||
|
ret += v.probability
|
||||||
|
return 1 - ret
|
||||||
|
|
||||||
|
$scope.redirectToDoorman = (doormanLocation) ->
|
||||||
|
$http.get(doormanLocation).then (res) ->
|
||||||
|
id = res.data.id
|
||||||
|
return $location.url("/doormen/#{id}")
|
||||||
|
|
||||||
|
$scope.createDoorman = () ->
|
||||||
|
if not $scope.isDoormanValid()
|
||||||
|
return
|
||||||
|
$http.post("/api/doormen", $scope.payload).
|
||||||
|
then((res, s) ->
|
||||||
|
$scope.redirectToDoorman(res.headers("location"))
|
||||||
|
).catch((res) ->
|
||||||
|
$scope.error = res.data
|
||||||
|
)
|
||||||
|
|
||||||
|
$scope.isDoormanValid = () ->
|
||||||
|
if not $scope.payload.name
|
||||||
|
return false
|
||||||
|
if $scope.payload.values.length < 2
|
||||||
|
return false
|
||||||
|
if Math.abs($scope.getProbabilityRemaining()) > epsilon
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
return
|
||||||
|
]
|
||||||
|
|
||||||
|
return
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
define ["app"], (app) ->
|
||||||
|
app.controller "sideBarCtrl", ($scope) ->
|
||||||
|
$scope.message = "Message from MyCtrl1"
|
||||||
|
return
|
||||||
|
|
||||||
|
return
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
require.config
|
||||||
|
baseUrl: "js"
|
||||||
|
|
||||||
|
# alias libraries paths. Must set 'angular'
|
||||||
|
paths:
|
||||||
|
"angular" : "/bower_components/angular/angular.min"
|
||||||
|
"angular-route": "/bower_components/angular-route/angular-route.min"
|
||||||
|
"angularAMD" : "/bower_components/angularAMD/angularAMD.min"
|
||||||
|
"foundation" : "/bower_components/angular-foundation/mm-foundation-tpls"
|
||||||
|
|
||||||
|
|
||||||
|
# Add angular modules that does not support AMD out of the box, put it in a shim
|
||||||
|
shim:
|
||||||
|
"angularAMD": ["angular"]
|
||||||
|
"angular-route": ["angular"]
|
||||||
|
"foundation" : ["angular"]
|
||||||
|
|
||||||
|
|
||||||
|
# kick start application
|
||||||
|
deps: ["app"]
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
var bower = require('gulp-bower');
|
||||||
|
var gulp = require('gulp');
|
||||||
|
var coffee = require('gulp-coffee');
|
||||||
|
var help = require('gulp-task-listing');
|
||||||
|
var clean = require('gulp-clean');
|
||||||
|
var sass = require('gulp-sass');
|
||||||
|
var rjs = require("gulp-rjs");
|
||||||
|
|
||||||
|
|
||||||
|
gulp.task('help', help);
|
||||||
|
|
||||||
|
gulp.task('bower', function() {
|
||||||
|
return bower()
|
||||||
|
.pipe(gulp.dest('bower_components/'))
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('sass', ['bower'], function () {
|
||||||
|
return gulp.src('./scss/*.scss')
|
||||||
|
.pipe(sass({includePaths: ['bower_components/foundation/scss']}))
|
||||||
|
.pipe(gulp.dest('./css'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('coffee', ['bower'], function() {
|
||||||
|
return gulp.src('coffee/**/*.coffee')
|
||||||
|
.pipe(coffee({bare: true}))
|
||||||
|
.pipe(gulp.dest('js/'))
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('clean', function(){
|
||||||
|
return gulp.src(["js/", "bower_components/", "css/"], {read: false})
|
||||||
|
.pipe(clean())
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
gulp.task('rjs', ['coffee'], function(){
|
||||||
|
return gulp.src('coffee/**/*.coffee')
|
||||||
|
.pipe(coffee())
|
||||||
|
.pipe(gulp.dest('./dist'))
|
||||||
|
.pipe(rjs({baseUrl:'.'}))
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
gulp.task('default', ['bower', 'sass', 'coffee']);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
gulp.task('dev', ['bower', 'sass', 'coffee'], function(){
|
||||||
|
gulp.watch('coffee/**/*.coffee', ['coffee']);
|
||||||
|
gulp.watch('./scss/*.scss', ['sass']);
|
||||||
|
});
|
||||||
Executable
Whitespace-only changes.
@@ -0,0 +1,44 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>doorman</title>
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
<script data-main="/js/main" src="/bower_components/requirejs/require.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="off-canvas-wrap">
|
||||||
|
<div class="inner-wrap">
|
||||||
|
|
||||||
|
<nav class="tab-bar">
|
||||||
|
<section class="middle tab-bar-section">
|
||||||
|
<h1 class="title">Doorman</h1>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="right-small">
|
||||||
|
<a class="right-off-canvas-toggle menu-icon" ><span></span></a>
|
||||||
|
</section>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<aside class="right-off-canvas-menu">
|
||||||
|
<ul class="off-canvas-list">
|
||||||
|
<li><a></a></li>
|
||||||
|
<li><a href="#/doormen">doormen</a></li>
|
||||||
|
<li><a href="#/newdoorman">new doorman</a></li>
|
||||||
|
<li><a href="#/apispec">api specification</a></li>
|
||||||
|
<li><a href="#/serverspec">server specification</a></li>
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section class="main-section">
|
||||||
|
<div class="large-12 small-12 columns" ng-view>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<a class="exit-off-canvas"></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"gulp": "~3.8.9",
|
||||||
|
"gulp-bower": "0.0.7",
|
||||||
|
"gulp-clean": "~0.3.1",
|
||||||
|
"gulp-coffee": "~2.2.0",
|
||||||
|
"gulp-sass": "^1.2.4",
|
||||||
|
"gulp-task-listing": "~0.3.0",
|
||||||
|
"node-sass": "~1.2.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"gulp-rjs": "^0.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<div class="row wab-apispect">
|
||||||
|
<div class="columns large-12">
|
||||||
|
<h2>Api specification</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row wab-apispect">
|
||||||
|
<table class="columns large-12">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th >api endpoint</th>
|
||||||
|
<th>method</th>
|
||||||
|
<th >description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><wab-api endpoint="/doormen"></wab-api></td>
|
||||||
|
<td><wab-method method="get"></wab-method></td>
|
||||||
|
<td>Get the list of all the public doormen.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><wab-api endpoint="/doormen"></wab-api></td>
|
||||||
|
<td><wab-method method="post"></wab-method></td>
|
||||||
|
<td>Create a new doorman.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><wab-api endpoint="/doormen/:id"></wab-api></td>
|
||||||
|
<td><wab-method method="get"></wab-method></td>
|
||||||
|
<td>Get the doorman</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><wab-api endpoint="/doormen/:id"></wab-api></td>
|
||||||
|
<td><wab-method method="put"></wab-method></td>
|
||||||
|
<td>Update the doorman with new probabilities</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><wab-api endpoint="/doormen/:id"></wab-api></td>
|
||||||
|
<td><wab-method method="delete"></wab-method></td>
|
||||||
|
<td>Delete the doorman</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<div class="row">
|
||||||
|
<div class="columns large-12">
|
||||||
|
<h2>{{ doorman.name }} </h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="columns large-12">
|
||||||
|
<span>doorman id: <code>{{ doorman.id }}</code></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" ng-repeat="value in doorman.values">
|
||||||
|
<div class="columns large-9 small-10">
|
||||||
|
<label >name<input type="text" placeholder="value name" ng-value="value.name" disabled></label>
|
||||||
|
</div>
|
||||||
|
<div class="columns large-3 small-2">
|
||||||
|
<label >probability<input type="number" placeholder="value probability" ng-model="value.probability" min="0" max="1" step="0.01"></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="large-12 small-12 columns">
|
||||||
|
<ul class="button-group">
|
||||||
|
<li><a ng-click="updateDoorman()" class="small button" ng-class="{disabled: !validate()}">Update</a></li>
|
||||||
|
<li><a ng-click="deleteDoorman()" class="small button">Delete</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="columns large-12">
|
||||||
|
<span>Error {{ error }} getting doorman {{ id }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<div class="row">
|
||||||
|
<div class="columns large-12">
|
||||||
|
<h2>doormen</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="columns large-12">
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="doorman in doormen"> <a href="#/doormen/{{ doorman.id }}"> {{ doorman.name }} </a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<h2>Welcome to the world best a/b testing library</h2>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="large-12 columns">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
|
||||||
|
<h2>New Doorman</h2>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<div class="row">
|
||||||
|
<div class="columns large-12">
|
||||||
|
<label >name<input type="text" placeholder="unique doorman name" ng-model="payload.name"></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" ng-repeat="value in payload.values">
|
||||||
|
<div class="columns large-8">
|
||||||
|
<label >value<input type="text" placeholder="value name" ng-model="value.name"></label>
|
||||||
|
</div>
|
||||||
|
<div class="columns large-3">
|
||||||
|
<label >probability<input type="number" placeholder="value probability" ng-model="value.probability" min="0" max="1" step="0.01"></label>
|
||||||
|
</div>
|
||||||
|
<div class="columns large-1">
|
||||||
|
<br>
|
||||||
|
<a class="button round tiny" ng-click="removeValue( $index )">-</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="columns large-8">
|
||||||
|
<label >name<input type="text" placeholder="value name" ng-model="value.name"></label>
|
||||||
|
</div>
|
||||||
|
<div class="columns large-3">
|
||||||
|
<label >probability<input type="number" placeholder="value probability" ng-model="value.probability" min="0" max="1" step="0.01"></label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="columns large-1">
|
||||||
|
<br>
|
||||||
|
<a class="button round tiny" ng-class="{disabled: !isValidValue()}" ng-click="addNewValue()">+</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" ng-show="error">
|
||||||
|
<div class="columns large-12">
|
||||||
|
<code style="width:100%; height:100%; display:block; margin-bottom: 1em;">{{ error }}</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="large-11 columns">
|
||||||
|
<button ng-click="createDoorman()" ng-class="{disabled: !isDoormanValid()}">create</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="large-1 columns">
|
||||||
|
<span >propability remaning: {{ getProbabilityRemaining() }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
@import "settings";
|
||||||
|
@import url(http://fonts.googleapis.com/css?family=Orbitron:400,900,500,700);
|
||||||
|
|
||||||
|
h1, h2{
|
||||||
|
font-family: 'Orbitron', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.wab-sidebar{
|
||||||
|
background: $ghost;
|
||||||
|
|
||||||
|
h1{
|
||||||
|
text-align: center;
|
||||||
|
color: $primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.wab-apispect{
|
||||||
|
h2{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,41 @@
|
|||||||
|
@import "settings";
|
||||||
|
@import "foundation";
|
||||||
|
@import "doorman";
|
||||||
|
|
||||||
|
// Or selectively include components
|
||||||
|
// @import
|
||||||
|
// "foundation/components/accordion",
|
||||||
|
// "foundation/components/alert-boxes",
|
||||||
|
// "foundation/components/block-grid",
|
||||||
|
// "foundation/components/breadcrumbs",
|
||||||
|
// "foundation/components/button-groups",
|
||||||
|
// "foundation/components/buttons",
|
||||||
|
// "foundation/components/clearing",
|
||||||
|
// "foundation/components/dropdown",
|
||||||
|
// "foundation/components/dropdown-buttons",
|
||||||
|
// "foundation/components/flex-video",
|
||||||
|
// "foundation/components/forms",
|
||||||
|
// "foundation/components/grid",
|
||||||
|
// "foundation/components/inline-lists",
|
||||||
|
// "foundation/components/joyride",
|
||||||
|
// "foundation/components/keystrokes",
|
||||||
|
// "foundation/components/labels",
|
||||||
|
// "foundation/components/magellan",
|
||||||
|
// "foundation/components/orbit",
|
||||||
|
// "foundation/components/pagination",
|
||||||
|
// "foundation/components/panels",
|
||||||
|
// "foundation/components/pricing-tables",
|
||||||
|
// "foundation/components/progress-bars",
|
||||||
|
// "foundation/components/reveal",
|
||||||
|
// "foundation/components/side-nav",
|
||||||
|
// "foundation/components/split-buttons",
|
||||||
|
// "foundation/components/sub-nav",
|
||||||
|
// "foundation/components/switch",
|
||||||
|
// "foundation/components/tables",
|
||||||
|
// "foundation/components/tabs",
|
||||||
|
// "foundation/components/thumbs",
|
||||||
|
// "foundation/components/tooltips",
|
||||||
|
// "foundation/components/top-bar",
|
||||||
|
// "foundation/components/type",
|
||||||
|
// "foundation/components/offcanvas",
|
||||||
|
// "foundation/components/visibility";
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
const STATIC_DIR_PATH = "static/"
|
||||||
|
|
||||||
|
func serveStatic(router *mux.Router) {
|
||||||
|
handler := func(w http.ResponseWriter, request *http.Request) {
|
||||||
|
vars := mux.Vars(request)
|
||||||
|
filepath := "/" + vars["path"]
|
||||||
|
w.Header().Set("Cache-Control", "public, max-age=43200")
|
||||||
|
http.ServeFile(w, request, path.Join(STATIC_DIR_PATH, filepath))
|
||||||
|
}
|
||||||
|
router.HandleFunc("/{path:.*}", handler)
|
||||||
|
}
|
||||||
Reference in new issue
Block a user