so mamy things...
This commit is contained in:
1 parent
f7a8d416c1
commit
78d50d9a6d
23 files changed
+431
-88
No files matched your search
@@ -16,8 +16,8 @@ func init() {
|
||||
|
||||
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))
|
||||
conn.Save(doormen.QuickNewDoormanDefinition("foo", "0.25", "0.75"))
|
||||
conn.Save(doormen.QuickNewDoormanDefinition("bar", "0.5", "0.5"))
|
||||
|
||||
req, err := http.NewRequest("GET", "http://bigtits.com/api/doormen", nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -44,11 +44,19 @@ func Create(root string) http.Handler {
|
||||
UpdateDoorman,
|
||||
}
|
||||
|
||||
getDoormanStatus := &simpleapi.Endpoint{
|
||||
"/doormen/{id}/status",
|
||||
`Returns the status of the doorman as a doorman client expect it.`,
|
||||
"GET",
|
||||
GetDoormanStatus,
|
||||
}
|
||||
|
||||
api := simpleapi.New(
|
||||
root,
|
||||
serverSpecification,
|
||||
createDoorman,
|
||||
allDoorman,
|
||||
getDoormanStatus,
|
||||
doorman,
|
||||
updateDoorman,
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@ func TestCreateDoorman(t *testing.T) {
|
||||
conn = inmemoryconnector.NewInMemoryDBConnector()
|
||||
payload := `
|
||||
{"name":"name",
|
||||
"values":[{"name":"red","probability":0.2},{"name":"blue","probability":0.8}],
|
||||
"values":[{"name":"red","probability":"1/5"},{"name":"blue","probability": "4/5"}],
|
||||
"emails": ["alice"]
|
||||
}`
|
||||
req, err := http.NewRequest(
|
||||
@@ -34,7 +34,7 @@ func TestCreateDoorman(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
CreateDoorman(w, req)
|
||||
if w.Code != 201 {
|
||||
t.Error("bad status code", w.Code)
|
||||
t.Error("bad status code", w.Code, "\n", w.Body)
|
||||
} else if location := w.Header().Get("location"); location == "" {
|
||||
t.Error("missing location header", location)
|
||||
} else if dor, _ := conn.GetDoorman(GetIdFromLocation(location)); dor == nil {
|
||||
|
||||
@@ -21,3 +21,24 @@ func GetDoorman(w http.ResponseWriter, r *http.Request) {
|
||||
encoder.Encode(doorman)
|
||||
}
|
||||
}
|
||||
|
||||
func GetDoormanStatus(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)
|
||||
}
|
||||
|
||||
if payload, err := doorman.AsDoormanUpdatePayload(); err != nil {
|
||||
Write500Error(w, err)
|
||||
} else {
|
||||
w.Write(payload)
|
||||
}
|
||||
|
||||
}
|
||||
+23
-13
@@ -4,18 +4,22 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
"math/big"
|
||||
"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"`
|
||||
Name string `json:"name"`
|
||||
Probability *big.Rat `json:"probability"`
|
||||
}
|
||||
|
||||
type doormanValue struct {
|
||||
Name string
|
||||
Probability string
|
||||
}
|
||||
|
||||
type DoormanId struct {
|
||||
@@ -43,10 +47,15 @@ func NewDoormanDefinition(name string, ownerEmails ...string) *DoormanDefinition
|
||||
|
||||
// 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 {
|
||||
func QuickNewDoormanDefinition(name string, probs ...string) *DoormanDefinition {
|
||||
ret := NewDoormanDefinition(name, "natasha@bigtits.com")
|
||||
for i, prob := range probs {
|
||||
ret.Values = append(ret.Values, &DoormanValue{"T" + strconv.Itoa(i), prob})
|
||||
r := new(big.Rat)
|
||||
_, err := fmt.Sscan(prob, r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ret.Values = append(ret.Values, &DoormanValue{"T" + strconv.Itoa(i), r})
|
||||
}
|
||||
if err := ret.Validate("natasha@bigtits.com"); err != nil {
|
||||
panic(err)
|
||||
@@ -79,18 +88,19 @@ func (dmd *DoormanDefinition) AsWriteAccess(email string) bool {
|
||||
}
|
||||
|
||||
func (dmd *DoormanDefinition) ValidateDoormanValueProbabilities() error {
|
||||
var sum float64 = 0
|
||||
sum, zero, one := big.NewRat(0, 1), big.NewRat(0, 1), big.NewRat(1, 1)
|
||||
for _, w := range dmd.Values {
|
||||
|
||||
if p := w.Probability; p < 0-epsilon || p > 1+epsilon {
|
||||
if p := w.Probability; p.Cmp(zero) < 0 || p.Cmp(one) > 0 {
|
||||
return errors.New(fmt.Sprintf("doorman value %v is out of range", p))
|
||||
} else {
|
||||
sum += p
|
||||
sum = new(big.Rat).Add(sum, p)
|
||||
}
|
||||
}
|
||||
if sum < 1-epsilon || sum > 1+epsilon {
|
||||
return errors.New("the sum of the probability must be 1.0")
|
||||
if sum.Cmp(one) != 0 {
|
||||
return errors.New("the sum of the probability must be 1")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -125,7 +135,7 @@ func (dmd *DoormanDefinition) CanBeUpdatedBy(dmd2 *DoormanDefinition) error {
|
||||
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))
|
||||
wu.Probabilities = make([]*big.Rat, len(dmd.Values))
|
||||
for i, value := range dmd.Values {
|
||||
wu.Probabilities[i] = value.Probability
|
||||
}
|
||||
|
||||
+23
-19
@@ -2,13 +2,17 @@ package doormen
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/didiercrunch/doorman/shared"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
var zero = big.NewRat(0, 1)
|
||||
|
||||
const epsilon float64 = 0.0001
|
||||
|
||||
func TestNewDoormanDefinition(t *testing.T) {
|
||||
dmd := NewDoormanDefinition("some_name")
|
||||
if dmd.Id == "" {
|
||||
@@ -33,40 +37,40 @@ func TestValidate(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0}, &DoormanValue{"foo", 0}}}
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", zero}, &DoormanValue{"foo", zero}}}
|
||||
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}}}
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", zero}, &DoormanValue{"", zero}}}
|
||||
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("bob"); err.Error() != "doorman value -0.25 is out of range" {
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", big.NewRat(-1, 4)}, &DoormanValue{"bar", big.NewRat(1, 2)}}}
|
||||
if err := dmd.Validate("bob"); err.Error() != "doorman value -1/4 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("bob"); err.Error() != "doorman value 2.5 is out of range" {
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", big.NewRat(5, 2)}, &DoormanValue{"bar", big.NewRat(1, 2)}}}
|
||||
if err := dmd.Validate("bob"); err.Error() != "doorman value 5/2 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("bob"); err.Error() != "the sum of the probability must be 1.0" {
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", big.NewRat(3, 4)}, &DoormanValue{"bar", big.NewRat(1, 2)}}}
|
||||
if err := dmd.Validate("bob"); err.Error() != "the sum of the probability must be 1" {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0.2}, &DoormanValue{"bar", 0.2}}}
|
||||
if err := dmd.Validate("bob"); err.Error() != "the sum of the probability must be 1.0" {
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", big.NewRat(1, 5)}, &DoormanValue{"bar", big.NewRat(1, 5)}}}
|
||||
if err := dmd.Validate("bob"); err.Error() != "the sum of the probability must be 1" {
|
||||
t.Error(err)
|
||||
}
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0.8}, &DoormanValue{"bar", 0.2}}}
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", big.NewRat(4, 5)}, &DoormanValue{"bar", big.NewRat(1, 5)}}}
|
||||
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"}}
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", big.NewRat(4, 5)}, &DoormanValue{"bar", big.NewRat(1, 5)}}, OwnerEmails: []string{"alice"}}
|
||||
if err := dmd.Validate("bob"); err.Error() != "'bob' is not allow to edit this doorman." {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -91,7 +95,7 @@ func TestAsJson(t *testing.T) {
|
||||
func TestAsDoormanUpdatePayload(t *testing.T) {
|
||||
wd := &DoormanDefinition{}
|
||||
wd.Id = bson.NewObjectId()
|
||||
wd.Values = []*DoormanValue{&DoormanValue{"C", 0.25}, &DoormanValue{"T1", 0.75}}
|
||||
wd.Values = []*DoormanValue{&DoormanValue{"C", big.NewRat(1, 4)}, &DoormanValue{"T1", big.NewRat(3, 4)}}
|
||||
data, err := wd.AsDoormanUpdatePayload()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -106,9 +110,9 @@ func TestAsDoormanUpdatePayload(t *testing.T) {
|
||||
}
|
||||
if len(wu.Probabilities) != 2 {
|
||||
t.Error("bad probability length")
|
||||
} else if math.Abs(wu.Probabilities[0]-0.25) > epsilon {
|
||||
} else if wu.Probabilities[0].Cmp(big.NewRat(1, 4)) != 0 {
|
||||
t.Error("bad first probability")
|
||||
} else if math.Abs(wu.Probabilities[1]-0.75) > epsilon {
|
||||
} else if wu.Probabilities[1].Cmp(big.NewRat(3, 4)) != 0 {
|
||||
t.Error("bad second probability")
|
||||
} else if wu.Timestamp == 0 {
|
||||
t.Error("no time specify")
|
||||
@@ -118,7 +122,7 @@ func TestAsDoormanUpdatePayload(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQuickNewDoormanDefinition(t *testing.T) {
|
||||
dmd := QuickNewDoormanDefinition("foo", 0.75, 0.25)
|
||||
dmd := QuickNewDoormanDefinition("foo", "0.75", "0.25")
|
||||
if dmd.Name != "foo" {
|
||||
t.Error()
|
||||
}
|
||||
@@ -127,11 +131,11 @@ func TestQuickNewDoormanDefinition(t *testing.T) {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
if dmd.Values[0].Name != "T0" || dmd.Values[0].Probability != 0.75 {
|
||||
if dmd.Values[0].Name != "T0" || dmd.Values[0].Probability.Cmp(big.NewRat(3, 4)) != 0 {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
if dmd.Values[1].Name != "T1" || dmd.Values[1].Probability != 0.25 {
|
||||
if dmd.Values[1].Name != "T1" || dmd.Values[1].Probability.Cmp(big.NewRat(1, 4)) != 0 {
|
||||
t.Error()
|
||||
}
|
||||
if len(dmd.OwnerEmails) != 1 && dmd.OwnerEmails[0] != "natasha@bigtits.com" {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package doormen
|
||||
|
||||
import (
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
func (val *DoormanValue) GetBSON() (interface{}, error) {
|
||||
ret := &doormanValue{val.Name, ""}
|
||||
txt, err := val.Probability.MarshalText()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.Probability = string(txt)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (val *DoormanValue) SetBSON(raw bson.Raw) error {
|
||||
v := new(doormanValue)
|
||||
if err := raw.Unmarshal(v); err != nil {
|
||||
return err
|
||||
}
|
||||
val.Name = v.Name
|
||||
val.Probability = new(big.Rat)
|
||||
return val.Probability.UnmarshalText([]byte(v.Probability))
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package doormen
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJsonEncoding(t *testing.T) {
|
||||
dmd := QuickNewDoormanDefinition("test json", "1/4", "3/4")
|
||||
data, err := json.Marshal(dmd)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
dmd2 := new(DoormanDefinition)
|
||||
if err = json.Unmarshal(data, dmd2); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !reflect.DeepEqual(dmd2, dmd) {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBsonEncoding(t *testing.T) {
|
||||
dmd := QuickNewDoormanDefinition("test bson", "1/4", "3/4")
|
||||
data, err := bson.Marshal(dmd)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
dmd2 := new(DoormanDefinition)
|
||||
if err = bson.Unmarshal(data, dmd2); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !reflect.DeepEqual(dmd2, dmd) {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func createMockDoorman(name string) *doormen.DoormanDefinition {
|
||||
return doormen.QuickNewDoormanDefinition(name, 0.5, 0.5)
|
||||
return doormen.QuickNewDoormanDefinition(name, "0.5", "0.5")
|
||||
}
|
||||
|
||||
func TestNewInMemoryDBConnector(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"directory": "app/bower_components"
|
||||
}
|
||||
+2
-1
@@ -22,6 +22,7 @@
|
||||
"angularAMD": "~0.2.1",
|
||||
"angular-foundation": "~0.5.1",
|
||||
"lodash": "~3.9.3",
|
||||
"font-awesome": "~4.3.0"
|
||||
"font-awesome": "~4.3.0",
|
||||
"bourbon": "~4.2.3"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
|
||||
define [
|
||||
"angularAMD"
|
||||
"lodash"
|
||||
"angular-route"
|
||||
"foundation"
|
||||
], (angularAMD) ->
|
||||
|
||||
], (angularAMD, _) ->
|
||||
app = angular.module("webapp", ["ngRoute", "mm.foundation"])
|
||||
app.config ($routeProvider) ->
|
||||
$routeProvider.when("/", angularAMD.route(
|
||||
@@ -39,6 +41,24 @@ define [
|
||||
document.getElementsByTagName("body")[0].style.visibility = ""
|
||||
return
|
||||
|
||||
app.controller "topbarCtrl", ["$scope", "$rootScope", "$location", ($scope, $rootScope, $location) ->
|
||||
|
||||
$scope.init = () ->
|
||||
$scope.email = localStorage.getItem("email")
|
||||
$scope.loggedIn = !!$scope.email
|
||||
if !$scope.loggedIn
|
||||
$location.path('/').replace()
|
||||
|
||||
$scope.logout = () ->
|
||||
localStorage.setItem("email", "")
|
||||
$scope.email = ""
|
||||
$scope.loggedIn = false
|
||||
$scope.$emit("logout")
|
||||
|
||||
$scope.init()
|
||||
$rootScope.$on('login', $scope.init)
|
||||
return
|
||||
]
|
||||
|
||||
|
||||
angularAMD.bootstrap(app)
|
||||
@@ -1,6 +1,8 @@
|
||||
define ["app", "lodash"], (app, _) ->
|
||||
define ["app", "lodash", "directives/ratinput"], (app, _, ratinput) ->
|
||||
|
||||
app.controller "DoormanCtrl", ["$scope", "$http","$routeParams","$location", ($scope, $http, $routeParams, $location) ->
|
||||
$scope.doorman = {values: []}
|
||||
$scope.somevalue = "2/6"
|
||||
$scope.apiurl = "/api/doormen/#{$routeParams.id}"
|
||||
$scope.user = localStorage.getItem("email")
|
||||
|
||||
@@ -20,10 +22,18 @@ define ["app", "lodash"], (app, _) ->
|
||||
$scope.initEmails = () ->
|
||||
[$scope.leftEmailColumns, $scope.rightEmailColumns] = _.chunk($scope.doorman.emails, 2)
|
||||
|
||||
$scope.rationalToFloat = (rat) ->
|
||||
if _.contains(rat, "/")
|
||||
num = Number(rat.substr(0, rat.indexOf("/")))
|
||||
denum = Number(rat.substr(rat.indexOf("/") + 1))
|
||||
return num / denum
|
||||
Number(rat)
|
||||
|
||||
|
||||
$scope.validate = ()->
|
||||
ret = 0
|
||||
for val in $scope.doorman.values
|
||||
ret += val.probability
|
||||
ret += $scope.rationalToFloat(val.probability)
|
||||
return Math.abs(ret - 1.0) < 0.000001
|
||||
|
||||
$scope.addNewAuthorEmail = () ->
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
define ["app"], (app) ->
|
||||
app.controller "HomeCtrl", ($scope) ->
|
||||
$scope.message = "message from home ctrl"
|
||||
app.controller "HomeCtrl", ['$scope', '$rootScope', ($scope, $rootScope) ->
|
||||
$scope.init = () ->
|
||||
$scope.loggedEmail = localStorage.getItem("email")
|
||||
|
||||
$scope.login = () ->
|
||||
if $scope.email
|
||||
localStorage.setItem("email", $scope.email)
|
||||
$scope.loggedEmail = $scope.email
|
||||
$scope.email = ""
|
||||
$scope.$emit("login")
|
||||
|
||||
return
|
||||
$scope.init()
|
||||
$rootScope.$on('logout', $scope.init)
|
||||
]
|
||||
@@ -1,10 +1,10 @@
|
||||
epsilon = 0.0001
|
||||
|
||||
define ["app", "../filters"], (app, filters) ->
|
||||
app.controller "NewDoormanCtrl", ["$scope", "$http", "$location", ($scope, $http, $location) ->
|
||||
define ["app", "lodash", "../filters", "directives/ratinput"], (app, _, filters, ratinput) ->
|
||||
app.controller "NewDoormanCtrl", ["$scope", "$http", "$location", "rationals", ($scope, $http, $location, rationals) ->
|
||||
$scope.error = ""
|
||||
$scope.user = localStorage.getItem("email")
|
||||
$scope.value = {probability: 0, name: ""}
|
||||
$scope.value = {probability: "1", name: ""}
|
||||
$scope.payload =
|
||||
name: ""
|
||||
values: []
|
||||
@@ -17,27 +17,34 @@ define ["app", "../filters"], (app, filters) ->
|
||||
if !$scope.isValidValue()
|
||||
return
|
||||
$scope.payload.values.push($scope.value)
|
||||
$scope.value = {name: "", probability: 0}
|
||||
$scope.value =
|
||||
name: ""
|
||||
probability: $scope.getProbabilityRemaining()
|
||||
|
||||
$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
|
||||
sum = rationals.sum(_.pluck($scope.payload.values, "probability")...)
|
||||
return rationals.minus("1", sum)
|
||||
|
||||
|
||||
$scope.redirectToDoorman = (doormanLocation) ->
|
||||
$http.get(doormanLocation).then (res) ->
|
||||
id = res.data.id
|
||||
return $location.url("/doormen/#{id}")
|
||||
|
||||
$scope.getPayload = () ->
|
||||
payload = _.cloneDeep($scope.payload)
|
||||
for value in payload.values
|
||||
value.probability = value.probability
|
||||
return payload
|
||||
|
||||
$scope.createDoorman = () ->
|
||||
if not $scope.isDoormanValid()
|
||||
return
|
||||
params = {params: {user: $scope.user}}
|
||||
$http.post("/api/doormen", $scope.payload, params).
|
||||
$http.post("/api/doormen", $scope.getPayload(), params).
|
||||
then((res, s) ->
|
||||
$scope.redirectToDoorman(res.headers("location"))
|
||||
).catch((res) ->
|
||||
@@ -49,9 +56,8 @@ define ["app", "../filters"], (app, filters) ->
|
||||
return false
|
||||
if $scope.payload.values.length < 2
|
||||
return false
|
||||
if Math.abs($scope.getProbabilityRemaining()) > epsilon
|
||||
return false
|
||||
return true
|
||||
rationals.equal($scope.getProbabilityRemaining(), "0" )
|
||||
|
||||
|
||||
$scope.addEmail = () ->
|
||||
if not $scope.newEmail
|
||||
@@ -64,6 +70,10 @@ define ["app", "../filters"], (app, filters) ->
|
||||
return
|
||||
$scope.payload.emails.splice(idx, 1);
|
||||
|
||||
$scope.getSum = () ->
|
||||
probs = _.pluck($scope.payload.values, "probability")
|
||||
return rationals.pp(rationals.sum(probs...))
|
||||
|
||||
|
||||
|
||||
return
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
template = """
|
||||
<div class="row rat-input">
|
||||
<div class="row" ng-show="!edit" >
|
||||
<div class="columns large-12" style="text-align: center;">
|
||||
<span class="fraction-box" ng-click="enableEdit()">
|
||||
<span ng-bind="nominator"></span> ⁄ <span ng-bind="denominator" ></span>
|
||||
<i class="fa fa-pencil-square-o" ng-show="!ngDisabled"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="edit" class="edit">
|
||||
<form ng-submit="edit=false">
|
||||
<div class="large-12 columns" style="text-align: center;">
|
||||
<input ng-model="nominator" type="text" ng-change="changeModel()" ng-disabled="ngDisabled">
|
||||
<span style="text-align: center;">⁄<span>
|
||||
<input ng-model="denominator" type="text" ng-change="changeModel()" ng-disabled="ngDisabled">
|
||||
<i class="fa fa-check" ng-click="edit=false"></i>
|
||||
<input type="submit" style="display:none;">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
define ["app", "angularAMD", "lodash", "services/rationals"], (app, angularAMD, _, rationals) ->
|
||||
|
||||
angularAMD.directive "ratInput", () ->
|
||||
directive =
|
||||
restrict: 'E',
|
||||
template: template
|
||||
scope:
|
||||
ngModel: "="
|
||||
ngDisabled: "="
|
||||
controller: [ '$scope', "rationals", ($scope, rationals) ->
|
||||
|
||||
$scope.getNominator = ()->
|
||||
if _.contains($scope.ngModel, "/")
|
||||
return Number($scope.ngModel.substr(0, $scope.ngModel.indexOf("/")))
|
||||
Number($scope.ngModel)
|
||||
|
||||
$scope.getDenominator = () ->
|
||||
if _.contains($scope.ngModel, "/")
|
||||
return Number($scope.ngModel.substr($scope.ngModel.indexOf("/") + 1))
|
||||
1
|
||||
|
||||
$scope.changeModel = ()->
|
||||
$scope.ngModel = rationals.newRat($scope.nominator, $scope.denominator)
|
||||
|
||||
$scope.enableEdit = () ->
|
||||
if not $scope.ngDisabled
|
||||
$scope.edit = true
|
||||
|
||||
$scope.init = () ->
|
||||
$scope.nominator = rationals.getNominator($scope.ngModel)
|
||||
$scope.denominator = $scope.getDenominator()
|
||||
|
||||
$scope.$watch 'ngModel', () ->
|
||||
$scope.init()
|
||||
|
||||
$scope.init()
|
||||
|
||||
]
|
||||
return directive
|
||||
@@ -0,0 +1,80 @@
|
||||
define ["app", "angularAMD", "lodash"], (app, angularAMD, _) ->
|
||||
|
||||
app.service "rationals", () ->
|
||||
service =
|
||||
getNominator: (rat) ->
|
||||
if _.contains(rat, "/")
|
||||
return Number(rat.substr(0, rat.indexOf("/")))
|
||||
Number(rat)
|
||||
|
||||
getDenominator: (rat) ->
|
||||
if _.contains(rat, "/")
|
||||
return Number(rat.substr(rat.indexOf("/") + 1))
|
||||
1
|
||||
|
||||
getNomDenom: (rat) ->
|
||||
return [service.getNominator(rat), service.getDenominator(rat)]
|
||||
|
||||
gcd: (a, b) ->
|
||||
if b == 0
|
||||
return a
|
||||
return service.gcd(b, a % b)
|
||||
|
||||
lcm: (a, b) ->
|
||||
return a * b / service.gcd(a, b)
|
||||
|
||||
changeDenominator: (rat, newDenom) ->
|
||||
[nom, denom] = service.getNomDenom(rat)
|
||||
return service.newRat( nom * newDenom / denom, newDenom)
|
||||
|
||||
add: (rat1, rat2) ->
|
||||
[nom, denom] = service.getNomDenom(rat2)
|
||||
[nNom, nDenom] = service.getNomDenom(rat1)
|
||||
gcd = service.gcd(nDenom, denom)
|
||||
|
||||
rNom = nom*nDenom + nNom * denom
|
||||
rDenom = denom * nDenom
|
||||
return service.newRat(rNom/gcd , rDenom/gcd)
|
||||
|
||||
minus: (rat1, rat2) ->
|
||||
return service.add(rat1, service.opposite(rat2))
|
||||
|
||||
reduce: (rat) ->
|
||||
[nom, denom] = service.getNomDenom(rat)
|
||||
gcd = service.gcd(nom, denom)
|
||||
return service.newRat(nom/gcd, denom/gcd)
|
||||
|
||||
opposite: (rat) ->
|
||||
[nom, denom] = service.getNomDenom(rat)
|
||||
return service.newRat(-nom, denom)
|
||||
|
||||
|
||||
sum: (rats...) ->
|
||||
ret = "0/1"
|
||||
for rat in rats
|
||||
ret = service.add(ret, rat)
|
||||
return service.reduce(ret)
|
||||
|
||||
equal: (rat1, rat2) ->
|
||||
[nom1, denom1] = service.getNomDenom(rat1)
|
||||
[nom2, denom2] = service.getNomDenom(rat2)
|
||||
return nom1 * denom2 == nom2 * denom1
|
||||
|
||||
pp: (x) ->
|
||||
[num, denum] = service.getNomDenom(service.reduce(x))
|
||||
if denum == 1
|
||||
return num
|
||||
return "#{num}/#{denum}"
|
||||
|
||||
|
||||
newRat: (nom , denom) ->
|
||||
if nom * denom >= 0
|
||||
return "#{Math.abs(nom)}/#{Math.abs(denom)}"
|
||||
return "#{-Math.abs(nom)}/#{Math.abs(denom)}"
|
||||
|
||||
return service
|
||||
|
||||
app.filter "ratpp", ["rationals", (rationals) ->
|
||||
filter = (n) ->
|
||||
return rationals.pp(n)
|
||||
]
|
||||
+1
-1
@@ -17,7 +17,7 @@ gulp.task('bower', function() {
|
||||
|
||||
gulp.task('sass', ['bower'], function () {
|
||||
return gulp.src('./scss/*.scss')
|
||||
.pipe(sass({includePaths: ['bower_components/foundation/scss']}))
|
||||
.pipe(sass({includePaths: ['bower_components/foundation/scss', 'bower_components/bourbon/app/assets/stylesheets']}))
|
||||
.pipe(gulp.dest('./css'));
|
||||
});
|
||||
|
||||
|
||||
+13
-9
@@ -9,35 +9,39 @@
|
||||
</head>
|
||||
|
||||
<body style="visibility:hidden;">
|
||||
|
||||
|
||||
<div ng-controller="topbarCtrl">
|
||||
<top-bar>
|
||||
<ul class="title-area">
|
||||
<li class="name">
|
||||
<h1><a href="#">Doorman</a></h1>
|
||||
<h1><a href="#/">Doorman</a></h1>
|
||||
</li>
|
||||
<li toggle-top-bar class="menu-icon"><a href="#">Menu</a></li>
|
||||
<li toggle-top-bar class="menu-icon"><a >Menu</a></li>
|
||||
</ul>
|
||||
|
||||
<top-bar-section>
|
||||
<!-- Right Nav Section -->
|
||||
<ul class="left show-for-large-up">
|
||||
<li>
|
||||
<a ng-bind="email" href="#"></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="right">
|
||||
<li has-dropdown>
|
||||
<a href="#">Dropdown</a>
|
||||
<ul top-bar-dropdown>
|
||||
<a >Dropdown</a>
|
||||
<ul top-bar-dropdown ng-show="loggedIn">
|
||||
<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>
|
||||
<li ng-click="logout()"><a href="#/">logout</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</top-bar-section>
|
||||
</top-bar>
|
||||
</div>
|
||||
|
||||
<div class="large-12 small-12 columns" ng-view></div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -12,19 +12,13 @@
|
||||
</div>
|
||||
|
||||
<div class="row" ng-repeat="value in doorman.values">
|
||||
<div class="columns large-8 small-10">
|
||||
<div class="columns large-7 small-10">
|
||||
<label >name<input type="text" placeholder="value name" ng-value="value.name" disabled></label>
|
||||
</div>
|
||||
<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>
|
||||
<rat-input ng-model="value.probability" ng-disabled="!isAdmin"></rat-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -55,11 +49,11 @@
|
||||
<label>
|
||||
new admin
|
||||
<div class="row collapse">
|
||||
<div class="columns large-10">
|
||||
<div class="columns large-10 small-10">
|
||||
|
||||
<input type="text" ng-model="newEmail"/>
|
||||
</div>
|
||||
<div class="columns large-2">
|
||||
<div class="columns large-2 small-2">
|
||||
|
||||
<a class="button postfix" ng-click="addNewAuthorEmail()" ng-class=" canAddNewEmail() ? '' : 'disabled' " >+</a>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
|
||||
<div class="row">
|
||||
<form ng-submit="login()" >
|
||||
<fieldset>
|
||||
<legend>Login</legend>
|
||||
<div class="large-12 columns">
|
||||
@@ -15,11 +16,12 @@
|
||||
<input type="text" placeholder="email" ng-model="email">
|
||||
</div>
|
||||
<div class="small-2 columns">
|
||||
<a class="button postfix" ng-click="login()">Login!</a>
|
||||
<input type="submit" class="button postfix" >Login!</input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<form>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-show="loggedEmail">
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form>
|
||||
<div class="row">
|
||||
<div class="columns large-12">
|
||||
<label >name<input type="text" placeholder="unique doorman name" ng-model="payload.name"></label>
|
||||
@@ -21,12 +20,16 @@
|
||||
<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>
|
||||
<label >probability
|
||||
<rat-input ng-model="value.probability"></rat-input>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="columns large-1">
|
||||
<br>
|
||||
<a class="button round tiny" ng-class="{disabled: !isValidValue()}" ng-click="addNewValue()">+</a>
|
||||
<a class="button round tiny" ng-class="{disabled: !isValidValue()}" ng-click="addNewValue()">
|
||||
<i class="fa fa-plus"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -35,11 +38,15 @@
|
||||
<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>
|
||||
<label >probability
|
||||
<rat-input ng-model="value.probability"></rat-input>
|
||||
</label>
|
||||
</div>
|
||||
<div class="columns large-1">
|
||||
<br>
|
||||
<a class="button round tiny" ng-click="removeValue( payload.values.length - $index - 1 )">-</a>
|
||||
<a class="button round tiny" ng-click="removeValue( payload.values.length - $index - 1 )">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -53,10 +60,12 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="columns large-11">
|
||||
<label>admin email <input type="text" placeholder="wild cards are allow here" ng-model="newEmail"></label>
|
||||
<label>admin email <input type="text" placeholder="admin email" ng-model="newEmail"></label>
|
||||
</div>
|
||||
<div class="columns large-1">
|
||||
<br><a class="button round tiny" ng-click="addEmail()" ng-class="{disabled: !newEmail}">+</a>
|
||||
<br><a class="button round tiny" ng-click="addEmail()" ng-class="{disabled: !newEmail}">
|
||||
<i class="fa fa-plus"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -65,7 +74,9 @@
|
||||
<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>
|
||||
<br><a class="button round tiny" ng-click="removeEmail(payload.emails.length - $index -1)" ng-disabled="email==user? 'disabled' : ''">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -85,8 +96,6 @@
|
||||
</div>
|
||||
|
||||
<div class="large-1 columns">
|
||||
<span >propability remaning: {{ getProbabilityRemaining() }}</span>
|
||||
<span >propability remaning: {{ getProbabilityRemaining() | ratpp}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@@ -1,4 +1,6 @@
|
||||
@import "settings";
|
||||
@import "bourbon";
|
||||
|
||||
// @import url(http://fonts.googleapis.com/css?family=Orbitron:400,900,500,700);
|
||||
@font-face {
|
||||
font-family: 'Orbitron';
|
||||
@@ -26,3 +28,36 @@ h1, h2{
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.rat-input{
|
||||
.fraction-box{
|
||||
font-family: monospace;
|
||||
font-size: 1.3em;
|
||||
border: 2px;
|
||||
padding: 0.3em;
|
||||
box-shadow: 2px 2px 1px #888;
|
||||
@include border-top-radius(5px);
|
||||
@include border-bottom-radius(5px);
|
||||
|
||||
i{
|
||||
font-size: 1em;
|
||||
margin-left: 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.fraction-box:hover{
|
||||
border: 2px;
|
||||
padding: 0.3em;
|
||||
box-shadow: 2px 2px 1px red;
|
||||
}
|
||||
|
||||
.edit{
|
||||
input{
|
||||
width: 4em;
|
||||
height: 2em;
|
||||
display: inline;
|
||||
padding: 0px;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user