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 @@
| 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 }} |