diff --git a/api/api.go b/api/api.go index 14441a3..c4638c2 100644 --- a/api/api.go +++ b/api/api.go @@ -5,7 +5,7 @@ import ( "net/http" ) -func Create() http.Handler { +func Create(root string) http.Handler { serverSpecification := &simpleapi.Endpoint{ "/server", "Get the server specification.", @@ -29,26 +29,32 @@ func Create() http.Handler { doorman := &simpleapi.Endpoint{ "/doormen/{id}", - "Get the doorman.", + "Get a specific doorman by id.", "GET", GetDoorman, } updateDoorman := &simpleapi.Endpoint{ "/doormen/{id}", - "Update the doorman with new probabilities.", + `Update the doorman with new probabilities or owner emails. The payload + must be a valid doorman with the same id than the targeted doorman. + The values of the PUT doorman will replace the values of the old + ones.`, "PUT", UpdateDoorman, } api := simpleapi.New( - "/api", + root, serverSpecification, createDoorman, allDoorman, doorman, updateDoorman, ) + api.EnableDocumentation("documentation") + api.AddMiddlewares(AuthenticationMiddleware) + api.InitRouter() return api } diff --git a/api/authentication.go b/api/authentication.go new file mode 100644 index 0000000..50ee566 --- /dev/null +++ b/api/authentication.go @@ -0,0 +1,29 @@ +package api + +import ( + "github.com/gorilla/context" + "net/http" +) + +const userKey = "user" + +func GetUser(req *http.Request) string { + if rv := context.Get(req, userKey); rv != nil { + return rv.(string) + } + return "" +} + +func SetUser(req *http.Request, user string) { + context.Set(req, userKey, user) +} + +func AuthenticationMiddleware(h http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + query := req.URL.Query() + user := query.Get("user") + SetUser(req, user) + h(w, req) + context.Clear(req) + } +} diff --git a/api/authentication_test.go b/api/authentication_test.go new file mode 100644 index 0000000..e67451d --- /dev/null +++ b/api/authentication_test.go @@ -0,0 +1,27 @@ +package api + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestAuthentication(t *testing.T) { + handler := func(w http.ResponseWriter, req *http.Request) { + if user := GetUser(req); user != "natasha" { + t.Error(user) + } + } + + req, err := http.NewRequest("GET", "http://example.com/foo?user=natasha", nil) + if err != nil { + t.Error(err) + return + } + w := httptest.NewRecorder() + AuthenticationMiddleware(handler)(w, req) + if user := GetUser(req); user != "" { + t.Error(user) + } + +} diff --git a/api/createdoorman.go b/api/createdoorman.go index 9d37912..bb1e882 100644 --- a/api/createdoorman.go +++ b/api/createdoorman.go @@ -18,7 +18,8 @@ func CreateDoorman(w http.ResponseWriter, request *http.Request) { return } wdef.Id = bson.NewObjectId() - if err := wdef.Validate(); err != nil { + + if err := wdef.Validate(GetUser(request)); err != nil { w.WriteHeader(400) fmt.Fprintln(w, "New doorman is invalid \n", err) return diff --git a/api/createdoorman_test.go b/api/createdoorman_test.go index fc491e4..afb620f 100644 --- a/api/createdoorman_test.go +++ b/api/createdoorman_test.go @@ -16,7 +16,11 @@ func GetIdFromLocation(p string) bson.ObjectId { func TestCreateDoorman(t *testing.T) { conn = inmemoryconnector.NewInMemoryDBConnector() - payload := `{"name":"name","values":[{"name":"red","probability":0.2},{"name":"blue","probability":0.8}]}` + payload := ` + {"name":"name", + "values":[{"name":"red","probability":0.2},{"name":"blue","probability":0.8}], + "emails": ["alice"] + }` req, err := http.NewRequest( "POST", "http://bigtits.com/api/doormen", @@ -26,6 +30,7 @@ func TestCreateDoorman(t *testing.T) { t.Error(err) return } + SetUser(req, "alice") w := httptest.NewRecorder() CreateDoorman(w, req) if w.Code != 201 { diff --git a/api/updatedoorman.go b/api/updatedoorman.go index abb4154..75ad5f9 100644 --- a/api/updatedoorman.go +++ b/api/updatedoorman.go @@ -22,7 +22,7 @@ func UpdateDoorman(w http.ResponseWriter, request *http.Request) { } wdef.Id = id - if err := wdef.Validate(); err != nil { + if err := wdef.Validate(GetUser(request)); err != nil { Write400Error(w, err) return } diff --git a/doormen/doormen.go b/doormen/doormen.go index c02f182..c1c2450 100644 --- a/doormen/doormen.go +++ b/doormen/doormen.go @@ -31,23 +31,24 @@ func (wId *DoormanId) AsJson() string { } type DoormanDefinition struct { - Id bson.ObjectId `json:"id,omitempty" bson:"_id"` - Name string `json:"name" bson:"name"` - Values []*DoormanValue `json:"values" bson:"values"` + Id bson.ObjectId `json:"id,omitempty" bson:"_id"` + Name string `json:"name" bson:"name"` + Values []*DoormanValue `json:"values" bson:"values"` + OwnerEmails []string `json:"emails" bson:"emails"` } -func NewDoormanDefinition(name string) *DoormanDefinition { - return &DoormanDefinition{Id: bson.NewObjectId(), Name: name} +func NewDoormanDefinition(name string, ownerEmails ...string) *DoormanDefinition { + return &DoormanDefinition{Id: bson.NewObjectId(), Name: name, OwnerEmails: ownerEmails} } // 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) + ret := NewDoormanDefinition(name, "natasha@bigtits.com") for i, prob := range probs { ret.Values = append(ret.Values, &DoormanValue{"T" + strconv.Itoa(i), prob}) } - if err := ret.Validate(); err != nil { + if err := ret.Validate("natasha@bigtits.com"); err != nil { panic(err) } return ret @@ -68,6 +69,15 @@ func (dmd *DoormanDefinition) ValidateDoormanValueNames() error { return nil } +func (dmd *DoormanDefinition) AsWriteAccess(email string) bool { + for _, email_ := range dmd.OwnerEmails { + if email_ == email { + return true + } + } + return false +} + func (dmd *DoormanDefinition) ValidateDoormanValueProbabilities() error { var sum float64 = 0 for _, w := range dmd.Values { @@ -84,7 +94,7 @@ func (dmd *DoormanDefinition) ValidateDoormanValueProbabilities() error { return nil } -func (dmd *DoormanDefinition) Validate() (err error) { +func (dmd *DoormanDefinition) Validate(author string) (err error) { switch { case dmd.Name == "": err = errors.New("name cannot be empty") @@ -94,6 +104,10 @@ func (dmd *DoormanDefinition) Validate() (err error) { err = dmd.ValidateDoormanValueNames() case dmd.ValidateDoormanValueProbabilities() != nil: err = dmd.ValidateDoormanValueProbabilities() + case len(dmd.OwnerEmails) < 1: + err = errors.New("A doorman needs at least one owner specify as 'email'.") + case !dmd.AsWriteAccess(author): + err = errors.New(fmt.Sprintf("'%s' is not allow to edit this doorman.", author)) } return } diff --git a/doormen/doormen_test.go b/doormen/doormen_test.go index 20e2581..8405541 100644 --- a/doormen/doormen_test.go +++ b/doormen/doormen_test.go @@ -21,45 +21,53 @@ func TestNewDoormanDefinition(t *testing.T) { func TestValidate(t *testing.T) { dmd := &DoormanDefinition{Name: ""} - if err := dmd.Validate(); err.Error() != "name cannot be empty" { + if err := dmd.Validate("bob"); err.Error() != "name cannot be empty" { t.Error(err) } dmd = &DoormanDefinition{Name: "foo"} - if err := dmd.Validate(); err.Error() != "need at least 2 values" { + if err := dmd.Validate("bob"); 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" { + if err := dmd.Validate("bob"); 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" { + if err := dmd.Validate("bob"); 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" { + if err := dmd.Validate("bob"); 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" { + if err := dmd.Validate("bob"); 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" { + if err := dmd.Validate("bob"); 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" { + if err := dmd.Validate("bob"); err.Error() != "the sum of the probability must be 1.0" { t.Error(err) } dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0.2}, &DoormanValue{"bar", 0.2}}} - if err := dmd.Validate(); err.Error() != "the sum of the probability must be 1.0" { + if err := dmd.Validate("bob"); err.Error() != "the sum of the probability must be 1.0" { + t.Error(err) + } + dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0.8}, &DoormanValue{"bar", 0.2}}} + if err := dmd.Validate("bob"); err.Error() != "A doorman needs at least one owner specify as 'email'." { + t.Error(err) + } + dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0.8}, &DoormanValue{"bar", 0.2}}, OwnerEmails: []string{"alice"}} + if err := dmd.Validate("bob"); err.Error() != "'bob' is not allow to edit this doorman." { t.Error(err) } } @@ -126,4 +134,21 @@ func TestQuickNewDoormanDefinition(t *testing.T) { if dmd.Values[1].Name != "T1" || dmd.Values[1].Probability != 0.25 { t.Error() } + if len(dmd.OwnerEmails) != 1 && dmd.OwnerEmails[0] != "natasha@bigtits.com" { + t.Error() + } +} + +func TestAsWriteAccess(t *testing.T) { + dmd := &DoormanDefinition{OwnerEmails: []string{"natasha@bigtits.com"}} + testCases := map[string]bool{ + "natasha@bigtits.com": true, + "natasha@bigtis.net": false, + } + for email, hasAccess := range testCases { + if hasAccess != dmd.AsWriteAccess(email) { + t.Error(email) + } + } + } diff --git a/params.yml b/params.yml index 72eab02..c1fe1df 100644 --- a/params.yml +++ b/params.yml @@ -17,7 +17,7 @@ host: 0.0.0.0 #localhost message_queue: nanomsg # nanomsg # nsq # determine which database to use -database: in_memory # mongo +database: mongo # mongo ################################################################################ diff --git a/server.go b/server.go index abd6b7f..f7d2862 100644 --- a/server.go +++ b/server.go @@ -19,14 +19,13 @@ func GetAddressToServe() string { func serveStatic2(w http.ResponseWriter, request *http.Request) { filepath := request.URL.Path - w.Header().Set("Cache-Control", "public, max-age=43200") - fmt.Println(STATIC_DIR_PATH, filepath, path.Join(STATIC_DIR_PATH, filepath)) + w.Header().Set("Cache-Control", "private, max-age=0, no-cache") http.ServeFile(w, request, path.Join(STATIC_DIR_PATH, filepath)) } func main() { mux := http.NewServeMux() - mux.Handle("/api/", api.Create()) + mux.Handle("/api/", api.Create("/api")) mux.HandleFunc("/", serveStatic2) address := GetAddressToServe() diff --git a/simpleapi/simpleapi.go b/simpleapi/simpleapi.go index 81bd79e..e299083 100644 --- a/simpleapi/simpleapi.go +++ b/simpleapi/simpleapi.go @@ -1,13 +1,17 @@ package simpleapi import ( + "encoding/json" + "fmt" "github.com/gorilla/mux" "mime" "net/http" "path" ) -func JSONHendler(handler http.HandlerFunc) http.HandlerFunc { +type MiddleWare func(handler http.HandlerFunc) http.HandlerFunc + +func JSONHandler(handler http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, request *http.Request) { w.Header().Add("Content-Type", mime.TypeByExtension(".json")) handler(w, request) @@ -21,25 +25,67 @@ type Endpoint struct { HandlerFunc http.HandlerFunc } +func (ep *Endpoint) MarshalJSON() ([]byte, error) { + m := map[string]string{ + "pattern": path.Join(ep.Pattern), + "description": ep.Description, + "method": ep.Method, + } + return json.Marshal(m) +} + type SimpleApi struct { - apiEndpoints []*Endpoint - router *mux.Router - prefix string + apiEndpoints []*Endpoint + router *mux.Router + prefix string + middlewares []MiddleWare + documentationRoute string } func New(prefix string, apiEndpoints ...*Endpoint) *SimpleApi { - api := &SimpleApi{apiEndpoints, nil, prefix} - api.initRouter() + api := &SimpleApi{apiEndpoints, nil, prefix, make([]MiddleWare, 0), ""} + api.InitRouter() return api } -func (api *SimpleApi) initRouter() { +func (api *SimpleApi) DocumentationHandler(w http.ResponseWriter, req *http.Request) { + enc := json.NewEncoder(w) + payload := map[string]interface{}{"endpoints": api.apiEndpoints} + if e := enc.Encode(payload); e != nil { + fmt.Fprintln(w, e) + } +} + +func (api *SimpleApi) EnableDocumentation(route string) { + if route == "" { + return + } + api.documentationRoute = route + api.router.Path(path.Join(api.prefix, route)). + HandlerFunc(JSONHandler(api.DocumentationHandler)) +} + +func (api *SimpleApi) AddMiddlewares(middlewares ...MiddleWare) { + api.middlewares = append(api.middlewares, middlewares...) +} + +func (api *SimpleApi) applyMiddlewares(handler http.HandlerFunc) http.HandlerFunc { + ret := handler + for _, middleware := range api.middlewares { + ret = middleware(ret) + } + return ret +} + +func (api *SimpleApi) InitRouter() { api.router = mux.NewRouter() for _, apiEndpoint := range api.apiEndpoints { + handler := JSONHandler(api.applyMiddlewares(apiEndpoint.HandlerFunc)) api.router.Path(path.Join(api.prefix, apiEndpoint.Pattern)). Methods(apiEndpoint.Method). - HandlerFunc(JSONHendler(apiEndpoint.HandlerFunc)) + HandlerFunc(handler) } + api.EnableDocumentation(api.documentationRoute) } func (api *SimpleApi) Set404Handler(handler http.HandlerFunc) { diff --git a/simpleapi/simpleapi_test.go b/simpleapi/simpleapi_test.go index dac6eca..686854c 100644 --- a/simpleapi/simpleapi_test.go +++ b/simpleapi/simpleapi_test.go @@ -135,3 +135,77 @@ func TestServePrefix(t *testing.T) { t.Error(rd) } } + +func TestDocumentationHandler(t *testing.T) { + api := New("/api", GetMockApiEndpoint("/foo/bar", "GET")) + req, err := http.NewRequest("GET", "http://example.com/foo", nil) + if err != nil { + t.Error(err) + } + w := httptest.NewRecorder() + api.DocumentationHandler(w, req) + if code := w.Code; code != 200 { + t.Error("bad status", code) + } + body := `{"endpoints":[{"description":"","method":"GET","pattern":"/foo/bar"}]}` + "\n" + if body != w.Body.String() { + t.Error() + } +} + +func TestEnableDocumentation(t *testing.T) { + api := New("/api", GetMockApiEndpoint("/foo/bar", "GET")) + api.EnableDocumentation("/doc") + ts := httptest.NewServer(api) + defer ts.Close() + res, err := http.Get(ts.URL + "/api/doc") + if err != nil { + t.Error(err) + return + } + if res.StatusCode != 200 { + t.Error("should have status 200 but got", res.StatusCode) + } +} + +func TestEnableDocumentationAndIniting(t *testing.T) { + api := New("/api", GetMockApiEndpoint("/foo/bar", "GET")) + api.EnableDocumentation("/doc") + api.InitRouter() + ts := httptest.NewServer(api) + defer ts.Close() + res, err := http.Get(ts.URL + "/api/doc") + if err != nil { + t.Error(err) + return + } + if res.StatusCode != 200 { + t.Error("should have status 200 but got", res.StatusCode) + } +} + +func TestMiddleWares(t *testing.T) { + api := New("/api", GetMockApiEndpoint("/foo", "GET")) + middleWare1 := func(f http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + r.URL.Path += "/1" + f(w, r) + } + } + middleWare2 := func(f http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + r.URL.Path += "/2" + f(w, r) + } + } + api.AddMiddlewares(middleWare1, middleWare2) + api.InitRouter() + ts := httptest.NewServer(api) + defer ts.Close() + if rd, err := GET(ts, "/api/foo"); err != nil { + t.Error(err) + } else if rd.Path != "/api/foo/2/1" { + t.Error(rd.Path) + } + +} diff --git a/static/bower.json b/static/bower.json index e52b01e..2cf305d 100644 --- a/static/bower.json +++ b/static/bower.json @@ -20,6 +20,8 @@ "requirejs-text": "~2.0.10", "html5-boilerplate": "~4.3.0", "angularAMD": "~0.2.1", - "angular-foundation": "~0.5.1" + "angular-foundation": "~0.5.1", + "lodash": "~3.9.3", + "font-awesome": "~4.3.0" } } diff --git a/static/coffee/app.coffee b/static/coffee/app.coffee index 3a9749b..bc8a295 100644 --- a/static/coffee/app.coffee +++ b/static/coffee/app.coffee @@ -34,9 +34,11 @@ define [ templateUrl: "partials/doorman.html" controller: "DoormanCtrl" controllerUrl: "controllers/doormanCtrl" - )).otherwise redirectTo: "/" + )).otherwise({redirectTo: "/"}) + + document.getElementsByTagName("body")[0].style.visibility = "" return - serverspec + angularAMD.bootstrap(app) diff --git a/static/coffee/controllers/apispecCtrl.coffee b/static/coffee/controllers/apispecCtrl.coffee index d3ff7b8..5e2cf97 100644 --- a/static/coffee/controllers/apispecCtrl.coffee +++ b/static/coffee/controllers/apispecCtrl.coffee @@ -1,8 +1,12 @@ define ["app", "angularAMD"], (app, angularAMD) -> - app.controller "ApispecCtrl", ($scope) -> + app.controller "ApispecCtrl",['$scope', '$http', ($scope, $http) -> + $http.get("/api/documentation").then (res)-> + $scope.endpoints = res.data.endpoints + $scope.methods = get: "GET" post: "POST" + ] angularAMD.directive "wabMethod", () -> directive = diff --git a/static/coffee/controllers/doormanCtrl.coffee b/static/coffee/controllers/doormanCtrl.coffee index da0c60b..0b65579 100644 --- a/static/coffee/controllers/doormanCtrl.coffee +++ b/static/coffee/controllers/doormanCtrl.coffee @@ -1,9 +1,14 @@ -define ["app"], (app) -> +define ["app", "lodash"], (app, _) -> app.controller "DoormanCtrl", ["$scope", "$http","$routeParams","$location", ($scope, $http, $routeParams, $location) -> $scope.doorman = {values: []} $scope.apiurl = "/api/doormen/#{$routeParams.id}" + $scope.user = localStorage.getItem("email") + $http.get($scope.apiurl).then((res) -> $scope.doorman = res.data + $scope.isAdmin = _.contains($scope.doorman.emails, $scope.user) + $scope.initEmails() + ).catch((res) -> if res.status == 404 $location.path("/doormen/error/#{res.status}/#{$routeParams.id}") @@ -12,19 +17,44 @@ define ["app"], (app) -> $scope.deleteDoorman = () -> console.log("not yet implemented") + $scope.initEmails = () -> + [$scope.leftEmailColumns, $scope.rightEmailColumns] = _.chunk($scope.doorman.emails, 2) + $scope.validate = ()-> ret = 0 for val in $scope.doorman.values ret += val.probability return Math.abs(ret - 1.0) < 0.000001 + $scope.addNewAuthorEmail = () -> + if not $scope.canAddNewEmail() + return + $scope.doorman.emails.push($scope.newEmail) + $scope.updateDoorman() + $scope.newEmail = "" + $scope.initEmails() + + $scope.removeEmail = (email) -> + $scope.doorman.emails = _.reject($scope.doorman.emails , (email_) -> email_ == email) + $scope.initEmails() + $scope.updateDoorman() + + $scope.canAddNewEmail = ()-> + if !$scope.newEmail or $scope.newEmail.length < 5 + return false + not _.contains($scope.doorman.emails, $scope.newEmail) + + $scope.updateDoorman = () -> if !$scope.validate() return - $http.put($scope.apiurl, $scope.doorman).then((ret)-> + params = {params: {user: $scope.user}} + $http.put($scope.apiurl, $scope.doorman, params).then((ret)-> console.log "update" ).catch((res) -> - d = 9 + $scope.error = + message: res.data + status : res.status ) ] return diff --git a/static/coffee/controllers/homeCtrl.coffee b/static/coffee/controllers/homeCtrl.coffee index 05b8789..6e77979 100644 --- a/static/coffee/controllers/homeCtrl.coffee +++ b/static/coffee/controllers/homeCtrl.coffee @@ -1,6 +1,11 @@ define ["app"], (app) -> app.controller "HomeCtrl", ($scope) -> $scope.message = "message from home ctrl" - return + $scope.loggedEmail = localStorage.getItem("email") + $scope.login = () -> + if $scope.email + localStorage.setItem("email", $scope.email) + $scope.loggedEmail = $scope.email + $scope.email = "" return diff --git a/static/coffee/controllers/newdoormanCtrl.coffee b/static/coffee/controllers/newdoormanCtrl.coffee index ddbf04f..291fb12 100644 --- a/static/coffee/controllers/newdoormanCtrl.coffee +++ b/static/coffee/controllers/newdoormanCtrl.coffee @@ -1,15 +1,17 @@ epsilon = 0.0001 -define ["app"], (app) -> - app.controller "NewDoormanCtrl", ["$scope", "$http", "$location", ($scope, $http, $location) -> +define ["app", "../filters"], (app, filters) -> + app.controller "NewDoormanCtrl", ["$scope", "$http", "$location", ($scope, $http, $location) -> $scope.error = "" - $scope.value = {} + $scope.user = localStorage.getItem("email") + $scope.value = {probability: 0, name: ""} $scope.payload = name: "" values: [] + emails: [$scope.user] $scope.isValidValue = () -> - return $scope.value.name and $scope.value.probability + return $scope.value.name $scope.addNewValue = () -> if !$scope.isValidValue() @@ -34,7 +36,8 @@ define ["app"], (app) -> $scope.createDoorman = () -> if not $scope.isDoormanValid() return - $http.post("/api/doormen", $scope.payload). + params = {params: {user: $scope.user}} + $http.post("/api/doormen", $scope.payload, params). then((res, s) -> $scope.redirectToDoorman(res.headers("location")) ).catch((res) -> @@ -50,6 +53,18 @@ define ["app"], (app) -> return false return true + $scope.addEmail = () -> + if not $scope.newEmail + return + $scope.payload.emails.push($scope.newEmail) + $scope.newEmail = "" + + $scope.removeEmail = (idx) -> + if $scope.payload.emails[idx] == $scope.user + return + $scope.payload.emails.splice(idx, 1); + + return ] diff --git a/static/coffee/filters.coffee b/static/coffee/filters.coffee new file mode 100644 index 0000000..8000215 --- /dev/null +++ b/static/coffee/filters.coffee @@ -0,0 +1,7 @@ +define ['angularAMD', 'app'], (angular, app) -> + app.filter 'reverse', -> + (items) -> + if !Array.isArray(items) + return false + items.slice().reverse() + return diff --git a/static/coffee/main.coffee b/static/coffee/main.coffee index 9769510..25e6863 100644 --- a/static/coffee/main.coffee +++ b/static/coffee/main.coffee @@ -7,6 +7,7 @@ require.config "angular-route": "/bower_components/angular-route/angular-route.min" "angularAMD" : "/bower_components/angularAMD/angularAMD.min" "foundation" : "/bower_components/angular-foundation/mm-foundation-tpls" + "lodash" : "/bower_components/lodash/lodash.min" # Add angular modules that does not support AMD out of the box, put it in a shim diff --git a/static/index.html b/static/index.html index 63c0108..390f0ad 100644 --- a/static/index.html +++ b/static/index.html @@ -4,10 +4,11 @@ doorman + - + diff --git a/static/package.json b/static/package.json index 1f843fe..75c3637 100644 --- a/static/package.json +++ b/static/package.json @@ -6,6 +6,7 @@ "gulp-coffee": "~2.2.0", "gulp-sass": "^1.2.4", "gulp-task-listing": "~0.3.0", + "gulp-util": "^3.0.3", "node-sass": "~1.2.3" }, "devDependencies": { diff --git a/static/partials/apispec.html b/static/partials/apispec.html index bc45fcc..28937eb 100644 --- a/static/partials/apispec.html +++ b/static/partials/apispec.html @@ -8,52 +8,19 @@
- + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + diff --git a/static/partials/doorman.html b/static/partials/doorman.html index 697a20b..6c99f59 100644 --- a/static/partials/doorman.html +++ b/static/partials/doorman.html @@ -12,31 +12,73 @@
-
+
-
- +
+
- +
-
+
  • Update
  • diff --git a/static/partials/home.html b/static/partials/home.html index 6ef8d77..1ed33de 100644 --- a/static/partials/home.html +++ b/static/partials/home.html @@ -4,8 +4,26 @@
+ +
+
+ Login +
+
+
+ +
+
+ Login! +
+
+
+
+
+ +
- {{ message }} + You are currently logged in as
diff --git a/static/partials/newdoorman.html b/static/partials/newdoorman.html index ef6fc85..8e59e21 100644 --- a/static/partials/newdoorman.html +++ b/static/partials/newdoorman.html @@ -11,30 +11,65 @@
-
-
- -
-
- -
-
-
- - +
+
+
+ doorman state + +
+
+ +
+
+ +
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+
+ - +
+
+
-
- -
-
- -
+
+
+ administrator -
-
- + +
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
- +
+
+ +
diff --git a/static/scss/_doorman.scss b/static/scss/_doorman.scss index 4df496f..15a7d30 100644 --- a/static/scss/_doorman.scss +++ b/static/scss/_doorman.scss @@ -1,5 +1,12 @@ @import "settings"; -@import url(http://fonts.googleapis.com/css?family=Orbitron:400,900,500,700); +// @import url(http://fonts.googleapis.com/css?family=Orbitron:400,900,500,700); +@font-face { + font-family: 'Orbitron'; + font-style: normal; + font-weight: 400; + src: local('Orbitron-Light'), local('Orbitron-Regular'), url(/scss/font/orbitron/HmnHiRzvcnQr8CjBje6GQvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'), url(/scss/font/orbitron/94ug0rEgQO_WuI_xKJMFc_esZW2xOQ-xsNqO47m55DA.woff) format('woff'); +} + h1, h2{ font-family: 'Orbitron', sans-serif;
api endpoint method description
Get the server specification
Get the list of all the public doormen.
Create a new doorman.
Get the doorman
Update the doorman with new probabilities
Delete the doorman
Return the state of the doormen as expected by the client
Return the state of the doormen as expected by the client
+ {{ endpoint.method }} + {{ endpoint.description }}