From f88ade0d73d81f7ba6e80c5b7b6781e94ef3b896 Mon Sep 17 00:00:00 2001 From: breel Date: Sat, 8 Aug 2020 12:01:49 -0600 Subject: [PATCH] impl entities except for PATCH --- {view => .view}/.aes/main.go | 0 {view => .view}/auth.go | 0 {view => .view}/auth_test.go | 0 {view => .view}/files.go | 0 {view => .view}/files_test.go | 0 {view => .view}/json.go | 0 {view => .view}/json_test.go | 0 {view => .view}/markdown.go | 0 {view => .view}/port.go | 0 {view => .view}/port_test.go | 0 {view => .view}/register.go | 0 {view => .view}/register_test.go | 0 {view => .view}/who.go | 0 {view => .view}/who_test.go | 0 main.go | 4 +- server/auth/scope.go | 2 +- server/auth/scope_test.go | 22 ++--- server/entities.go | 161 ++++++++++++++++++++++++++++++- server/files.go | 8 +- server/middleware.go | 7 ++ server/response.go | 4 + server/rest.go | 6 +- server/rest_test.go | 4 - server/users.go | 4 +- storage/driver/boltdb.go | 6 +- 25 files changed, 195 insertions(+), 33 deletions(-) rename {view => .view}/.aes/main.go (100%) rename {view => .view}/auth.go (100%) rename {view => .view}/auth_test.go (100%) rename {view => .view}/files.go (100%) rename {view => .view}/files_test.go (100%) rename {view => .view}/json.go (100%) rename {view => .view}/json_test.go (100%) rename {view => .view}/markdown.go (100%) rename {view => .view}/port.go (100%) rename {view => .view}/port_test.go (100%) rename {view => .view}/register.go (100%) rename {view => .view}/register_test.go (100%) rename {view => .view}/who.go (100%) rename {view => .view}/who_test.go (100%) diff --git a/view/.aes/main.go b/.view/.aes/main.go similarity index 100% rename from view/.aes/main.go rename to .view/.aes/main.go diff --git a/view/auth.go b/.view/auth.go similarity index 100% rename from view/auth.go rename to .view/auth.go diff --git a/view/auth_test.go b/.view/auth_test.go similarity index 100% rename from view/auth_test.go rename to .view/auth_test.go diff --git a/view/files.go b/.view/files.go similarity index 100% rename from view/files.go rename to .view/files.go diff --git a/view/files_test.go b/.view/files_test.go similarity index 100% rename from view/files_test.go rename to .view/files_test.go diff --git a/view/json.go b/.view/json.go similarity index 100% rename from view/json.go rename to .view/json.go diff --git a/view/json_test.go b/.view/json_test.go similarity index 100% rename from view/json_test.go rename to .view/json_test.go diff --git a/view/markdown.go b/.view/markdown.go similarity index 100% rename from view/markdown.go rename to .view/markdown.go diff --git a/view/port.go b/.view/port.go similarity index 100% rename from view/port.go rename to .view/port.go diff --git a/view/port_test.go b/.view/port_test.go similarity index 100% rename from view/port_test.go rename to .view/port_test.go diff --git a/view/register.go b/.view/register.go similarity index 100% rename from view/register.go rename to .view/register.go diff --git a/view/register_test.go b/.view/register_test.go similarity index 100% rename from view/register_test.go rename to .view/register_test.go diff --git a/view/who.go b/.view/who.go similarity index 100% rename from view/who.go rename to .view/who.go diff --git a/view/who_test.go b/.view/who_test.go similarity index 100% rename from view/who_test.go rename to .view/who_test.go diff --git a/main.go b/main.go index 8f691d3..ac1b6e2 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,6 @@ import ( "local/dndex/config" "local/dndex/server" "local/dndex/storage" - "local/dndex/view" "log" ) @@ -14,9 +13,8 @@ func main() { c := config.New() log.Println(c) g := storage.NewRateLimitedGraph() - view.GitCommit = GitCommit server.GitCommit = GitCommit - if err := view.JSON(g); err != nil { + if err := server.Listen(g); err != nil { panic(err) } } diff --git a/server/auth/scope.go b/server/auth/scope.go index 9c38aa3..bf09717 100644 --- a/server/auth/scope.go +++ b/server/auth/scope.go @@ -46,7 +46,7 @@ func (s *Scope) fromToken(r *http.Request) bool { } func (s *Scope) fromPath(path string) bool { - if !strings.HasPrefix(path, "/entity/") { + if !strings.HasPrefix(path, "/entities/") { return false } paths := strings.Split(path, "/") diff --git a/server/auth/scope_test.go b/server/auth/scope_test.go index c40f5d1..c019e7f 100644 --- a/server/auth/scope_test.go +++ b/server/auth/scope_test.go @@ -56,29 +56,29 @@ func TestScopeFromPath(t *testing.T) { ok bool id string }{ - "/": {}, - "/hello": {}, - "/hello/": {}, - "/hello/entity": {}, - "/entity": {}, - "/entity/": {}, - "/entity/id": { + "/": {}, + "/hello": {}, + "/hello/": {}, + "/hello/entities": {}, + "/entities": {}, + "/entities/": {}, + "/entities/id": { ok: true, id: "id", }, - "/entity/id/": { + "/entities/id/": { ok: true, id: "id", }, - "/entity/id/excess": { + "/entities/id/excess": { ok: true, id: "id", }, - "/entity/id#excess": { + "/entities/id#excess": { ok: true, id: "id", }, - "/entity/id?excess": { + "/entities/id?excess": { ok: true, id: "id", }, diff --git a/server/entities.go b/server/entities.go index d97f930..07fc39d 100644 --- a/server/entities.go +++ b/server/entities.go @@ -1,10 +1,31 @@ package server import ( + "encoding/json" + "errors" + "io" + "local/dndex/storage/entity" + "local/dndex/storage/operator" + "log" "net/http" + "path" + "strings" + + "gopkg.in/mgo.v2/bson" ) +type shortEntity struct { + Name string + ID string +} + func (rest *REST) entities(w http.ResponseWriter, r *http.Request) { + if scope, err := rest.entityScope(r); err != nil { + rest.respNotFound(w) + return + } else if r.Method != http.MethodGet && len(scope) > 1 { + r.Method = http.MethodPatch + } switch r.Method { case http.MethodPut: rest.entitiesReplace(w, r) @@ -17,26 +38,156 @@ func (rest *REST) entities(w http.ResponseWriter, r *http.Request) { case http.MethodDelete: rest.entitiesDelete(w, r) default: - http.NotFound(w, r) + rest.respNotFound(w) } } func (rest *REST) entitiesCreate(w http.ResponseWriter, r *http.Request) { - http.Error(w, "not impl", http.StatusNotImplemented) + scope := rest.scope(r) + entityScope, _ := rest.entityScope(r) + one, err := rest.entityParse(r.Body) + if err != nil { + rest.respBadRequest(w, err.Error()) + return + } + one.ID = entityScope[0] + if err := rest.g.Insert(r.Context(), scope.Namespace, one); err != nil { + rest.respError(w, err) + return + } + rest.entitiesGet(w, r) } func (rest *REST) entitiesDelete(w http.ResponseWriter, r *http.Request) { - http.Error(w, "not impl", http.StatusNotImplemented) + scope := rest.scope(r) + if err := rest.g.Delete(r.Context(), scope.Namespace, bson.M{entity.ID: scope.EntityID}); err != nil { + rest.respError(w, err) + return + } + rest.respOK(w) } func (rest *REST) entitiesGet(w http.ResponseWriter, r *http.Request) { - http.Error(w, "not impl", http.StatusNotImplemented) + entityScope, _ := rest.entityScope(r) + switch len(entityScope) { + case 0: + rest.entitiesList(w, r) + case 1: + rest.entitiesGetOne(w, r) + default: + rest.entitiesGetOneSub(w, r) + } +} + +func (rest *REST) entitiesGetOne(w http.ResponseWriter, r *http.Request) { + scope := rest.scope(r) + entityScope, _ := rest.entityScope(r) + one, err := rest.g.Get(r.Context(), scope.Namespace, entityScope[0]) + if err != nil { + rest.respNotFound(w) + return + } + rest.respMap(w, entityScope[0], one) +} + +func (rest *REST) entitiesGetOneSub(w http.ResponseWriter, r *http.Request) { + entityScope, _ := rest.entityScope(r) + scope := rest.scope(r) + one, err := rest.g.Get(r.Context(), scope.Namespace, scope.EntityID) + if err != nil { + rest.respNotFound(w) + return + } + b, err := bson.Marshal(one) + if err != nil { + rest.respError(w, err) + return + } + var m bson.M + err = bson.Unmarshal(b, &m) + if err != nil { + rest.respError(w, err) + return + } + entityScope = entityScope[1:] + for len(entityScope) > 1 { + k := entityScope[0] + entityScope = entityScope[1:] + subm, ok := m[k] + if !ok { + m = nil + break + } + mm, ok := subm.(bson.M) + if !ok { + m = nil + break + } + m = mm + } + rest.respMap(w, scope.EntityID, m[entityScope[0]]) +} + +func (rest *REST) entitiesList(w http.ResponseWriter, r *http.Request) { + scope := rest.scope(r) + entities, err := rest.g.List(r.Context(), scope.Namespace) + if err != nil { + rest.respError(w, err) + return + } + short := make([]shortEntity, len(entities)) + for i := range entities { + short[i] = shortEntity{Name: entities[i].Name, ID: entities[i].ID} + } + rest.respMap(w, scope.Namespace, short) } func (rest *REST) entitiesReplace(w http.ResponseWriter, r *http.Request) { - http.Error(w, "not impl", http.StatusNotImplemented) + scope := rest.scope(r) + one, err := rest.entityParse(r.Body) + if err != nil { + rest.respBadRequest(w, err.Error()) + return + } + if one.ID != scope.EntityID && one.ID != "" { + rest.respBadRequest(w, "cannot change primary key") + return + } + one.ID = scope.EntityID + err = rest.g.Update(r.Context(), scope.Namespace, bson.M{entity.ID: scope.EntityID}, operator.SetMany{Value: one}) + if err != nil { + rest.respError(w, err) + return + } + rest.entitiesGet(w, r) } func (rest *REST) entitiesUpdate(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method) + log.Println(rest.entityScope(r)) http.Error(w, "not impl", http.StatusNotImplemented) } + +func (rest *REST) entityParse(r io.Reader) (entity.One, error) { + var one entity.One + err := json.NewDecoder(r).Decode(&one) + return one, err +} + +func (rest *REST) entityScope(r *http.Request) ([]string, error) { + p := r.URL.Path + if path.Dir(p) == path.Base(p) { + if r.Method == http.MethodGet { + return []string{}, nil + } + return nil, errors.New("nothing specified") + } + ps := strings.Split(p, "/") + ps2 := []string{} + for i := range ps { + if ps[i] != "" { + ps2 = append(ps2, ps[i]) + } + } + return ps2, nil +} diff --git a/server/files.go b/server/files.go index 4da636b..0f0eaaf 100644 --- a/server/files.go +++ b/server/files.go @@ -10,7 +10,7 @@ import ( func (rest *REST) files(w http.ResponseWriter, r *http.Request) { if len(r.URL.Path) < 2 { - http.NotFound(w, r) + rest.respNotFound(w) return } switch r.Method { @@ -23,7 +23,7 @@ func (rest *REST) files(w http.ResponseWriter, r *http.Request) { case http.MethodDelete: rest.filesDelete(w, r) default: - http.NotFound(w, r) + rest.respNotFound(w) } } @@ -63,7 +63,7 @@ func (rest *REST) filesDelete(w http.ResponseWriter, r *http.Request) { func (rest *REST) filesGet(w http.ResponseWriter, r *http.Request) { localPath := rest.filesPath(r) if stat, err := os.Stat(localPath); os.IsNotExist(err) { - http.NotFound(w, r) + rest.respNotFound(w) return } else if err != nil || stat.IsDir() { rest.respConflict(w) @@ -83,7 +83,7 @@ func (rest *REST) filesGet(w http.ResponseWriter, r *http.Request) { func (rest *REST) filesUpdate(w http.ResponseWriter, r *http.Request) { localPath := rest.filesPath(r) if stat, err := os.Stat(localPath); os.IsNotExist(err) { - http.NotFound(w, r) + rest.respNotFound(w) return } else if err != nil || stat.IsDir() { rest.respConflict(w) diff --git a/server/middleware.go b/server/middleware.go index b0ad32a..85a503f 100644 --- a/server/middleware.go +++ b/server/middleware.go @@ -9,6 +9,13 @@ import ( "time" ) +func (rest *REST) scoped(foo http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + rest.scope(r) + foo(w, r) + } +} + func (rest *REST) delay(foo http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { select { diff --git a/server/response.go b/server/response.go index 03d9bf5..11e4400 100644 --- a/server/response.go +++ b/server/response.go @@ -10,6 +10,10 @@ func (rest *REST) respOK(w http.ResponseWriter) { rest.respMap(w, "ok", true) } +func (rest *REST) respNotFound(w http.ResponseWriter) { + rest.respMapStatus(w, "error", "not found", http.StatusNotFound) +} + func (rest *REST) respConflict(w http.ResponseWriter) { rest.respMapStatus(w, "error", "collision found", http.StatusConflict) } diff --git a/server/rest.go b/server/rest.go index 49fe0c8..73a1baf 100644 --- a/server/rest.go +++ b/server/rest.go @@ -7,6 +7,7 @@ import ( "local/dndex/storage" "local/router" "net/http" + "strings" ) type REST struct { @@ -43,7 +44,10 @@ func NewREST(g storage.RateLimitedGraph) (*REST, error) { for path, foo := range paths { bar := foo bar = rest.shift(bar) - bar = rest.auth(bar) + bar = rest.scoped(bar) + if !strings.HasPrefix(path, "users/") { + bar = rest.auth(bar) + } bar = rest.defend(bar) bar = rest.delay(bar) if err := rest.router.Add(path, bar); err != nil { diff --git a/server/rest_test.go b/server/rest_test.go index b2e3889..a36f95d 100644 --- a/server/rest_test.go +++ b/server/rest_test.go @@ -114,19 +114,15 @@ func TestRESTRouter(t *testing.T) { }, fmt.Sprintf("/entities/%s/connections/", testEntityID): { method: http.MethodPost, - is404: true, }, fmt.Sprintf("/entities/%s/connections/", testEntityID): { method: http.MethodPut, - is404: true, }, fmt.Sprintf("/entities/%s/connections/", testEntityID): { method: http.MethodPatch, - is404: true, }, fmt.Sprintf("/entities/%s/connections", testEntityID): { method: http.MethodPost, - is404: true, }, fmt.Sprintf("/entities/%s/connections", testEntityID): { method: http.MethodPut, diff --git a/server/users.go b/server/users.go index c5db016..13563ba 100644 --- a/server/users.go +++ b/server/users.go @@ -9,7 +9,7 @@ func (rest *REST) users(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPost: default: - http.NotFound(w, r) + rest.respNotFound(w) } switch r.URL.Path { case "/register": @@ -17,7 +17,7 @@ func (rest *REST) users(w http.ResponseWriter, r *http.Request) { case "/login": rest.usersLogin(w, r) default: - http.NotFound(w, r) + rest.respNotFound(w) } } diff --git a/storage/driver/boltdb.go b/storage/driver/boltdb.go index 920b30c..465319e 100644 --- a/storage/driver/boltdb.go +++ b/storage/driver/boltdb.go @@ -224,8 +224,10 @@ func matches(doc, filter bson.M) bool { } } default: - if fmt.Sprint(v) != fmt.Sprint(doc[k]) { - return false + if fmt.Sprint(v) != "nil" && fmt.Sprint(v) != "" { + if fmt.Sprint(v) != fmt.Sprint(doc[k]) { + return false + } } } }