added doormen ownership

This commit is contained in:
didier amyot committed 2015-06-14 23:21:42 -04:00
1 parent f0628e4682
commit f7a8d416c1
27 files changed
+475 -112

No files matched your search

+10 -4
View File
@@ -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
}
+29
View File
@@ -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)
}
}
+27
View File
@@ -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)
}
}
+2 -1
View File
@@ -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
+6 -1
View File
@@ -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 {
+1 -1
View File
@@ -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
}
+19 -5
View File
@@ -34,20 +34,21 @@ type DoormanDefinition struct {
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
}
+34 -9
View File
@@ -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)
}
}
}
+1 -1
View File
@@ -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
################################################################################
+2 -3
View File
@@ -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()
+51 -5
View File
@@ -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
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) {
+74
View File
@@ -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)
}
}
+3 -1
View File
@@ -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"
}
}
+4 -2
View File
@@ -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)
+5 -1
View File
@@ -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 =
+33 -3
View File
@@ -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
+6 -1
View File
@@ -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
@@ -1,15 +1,17 @@
epsilon = 0.0001
define ["app"], (app) ->
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
]
+7
View File
@@ -0,0 +1,7 @@
define ['angularAMD', 'app'], (angular, app) ->
app.filter 'reverse', ->
(items) ->
if !Array.isArray(items)
return false
items.slice().reverse()
return
+1
View File
@@ -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
+2 -1
View File
@@ -4,10 +4,11 @@
<head>
<title>doorman</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.min.css">
<script data-main="/js/main" src="/bower_components/requirejs/require.js"></script>
</head>
<body>
<body style="visibility:hidden;">
<top-bar>
+1
View File
@@ -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": {
+7 -40
View File
@@ -8,52 +8,19 @@
<div class="row wab-apispect">
<table class="columns large-12">
<thead>
<tr>
<tr >
<th >api endpoint</th>
<th>method</th>
<th >description</th>
</tr>
</thead>
<tbody>
<tr>
<td><wab-api endpoint="/server"></wab-api></td>
<td><wab-method method="get"></wab-method></td>
<td>Get the server specification</td>
</tr>
<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>
<tr>
<td><wab-api endpoint="/doormen/:id/status"></wab-api></td>
<td><wab-method method="get"></wab-method></td>
<td>Return the state of the doormen as expected by the client</td>
</tr>
<tr>
<td><wab-api endpoint="/doormen/:id/result?data=<base64>"></wab-api></td>
<td><wab-method method="get"></wab-method></td>
<td>Return the state of the doormen as expected by the client</td>
<tr ng-repeat="endpoint in endpoints">
<td><wab-api endpoint="{{ endpoint.pattern }}"></wab-api></td>
<td>
<code style="width:5em; display: block; text-align: center;">{{ endpoint.method }}</code>
</td>
<td >{{ endpoint.description }}</td>
</tr>
</tbody>
+57 -15
View File
@@ -12,31 +12,73 @@
</div>
<div class="row" ng-repeat="value in doorman.values">
<div class="columns large-9 small-10">
<div class="columns large-8 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 class="columns large-4 small-2">
<label >
probability
<input type="number"
placeholder="value probability"
ng-model="value.probability"
ng-disabled="!isAdmin"
min="0"
max="1"
step="0.01"></label>
</div>
</div>
<!-- <div class="row">
<div class="columns large-12 small-12">
<h3>try it!</h3>
</div>
</div>
<div class="row">
<textarea class="large-offset-1 small-offset-1 large-10 small-10 columns">
</textarea>
<div class="large-4 columns">
<ul>
<li ng-repeat="email in leftEmailColumns">
<span ng-bind="email"></span>
<i class="fa fa-times right"
ng-click="removeEmail(email)"
style="cursor: pointer;"
ng-show="doorman.emails.length > 1 && isAdmin && email != user"
></i>
</li>
</ul>
</div>
<div class="large-4 columns">
<ul>
<li ng-repeat="email in rightEmailColumns">
<span ng-bind="email"></span>
<i class="fa fa-times right"></i>
</li>
</ul>
</div>
<div class="large-4 columns">
<div>
<label>
new admin
<div class="row collapse">
<div class="columns large-10">
<input type="text" ng-model="newEmail"/>
</div>
<div class="columns large-2">
<a class="button postfix" ng-click="addNewAuthorEmail()" ng-class=" canAddNewEmail() ? '' : 'disabled' " >+</a>
</div>
</div>
</label>
</div>
</div>
</div>
<div class="row">
<div class="large-offset-1 small-offset-1 large-10 small-10 columns">
<a href="#" class="button tiny">try</a>
<div class="row" ng-show="error">
<div class="columns large-12">
<code style="width:100%; height:100%; display:block; margin-bottom: 1em;">
<b>{{ error.status }}</b><br>&nbsp; {{ error.message }}
</code>
</div>
</div> -->
</div>
<div class="row">
<div class="row" ng-show="isAdmin">
<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>
+19 -1
View File
@@ -4,8 +4,26 @@
</div>
</div>
<div class="row">
<fieldset>
<legend>Login</legend>
<div class="large-12 columns">
{{ message }}
<div class="row collapse">
<div class="small-10 columns">
<input type="text" placeholder="email" ng-model="email">
</div>
<div class="small-2 columns">
<a class="button postfix" ng-click="login()">Login!</a>
</div>
</div>
</div>
</fieldset>
</div>
<div class="row" ng-show="loggedEmail">
<div class="large-12 columns">
You are currently logged in as <code ng-bind="loggedEmail"></code>
</div>
</div>
+47 -12
View File
@@ -11,18 +11,10 @@
</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-12">
<fieldset>
<legend>doorman state</legend>
<div class="row">
<div class="columns large-8">
@@ -38,6 +30,49 @@
</div>
</div>
<div class="row" ng-repeat="value in payload.values | reverse">
<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( payload.values.length - $index - 1 )">-</a>
</div>
</div>
</fieldset>
</div>
</div>
<div class="row">
<div class="columns large-12">
<fieldset>
<legend>administrator</legend>
<div class="row">
<div class="columns large-11">
<label>admin email <input type="text" placeholder="wild cards are allow here" ng-model="newEmail"></label>
</div>
<div class="columns large-1">
<br><a class="button round tiny" ng-click="addEmail()" ng-class="{disabled: !newEmail}">+</a>
</div>
</div>
<div class="row" ng-repeat="email in payload.emails | reverse">
<div class="columns large-11">
<label>admin email <input type="text" ng-disabled="email==user" placeholder="wild cards are allow here" ng-model="email"></label>
</div>
<div class="columns large-1">
<br><a class="button round tiny" ng-click="removeEmail(payload.emails.length - $index -1)" ng-disabled="email==user? 'disabled' : ''">-</a>
</div>
</div>
</fieldset>
</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>
+8 -1
View File
@@ -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;