impl entities except for PATCH

master
breel 2020-08-08 12:01:49 -06:00
parent 1655a9b83a
commit f88ade0d73
25 changed files with 195 additions and 33 deletions

View File

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

View File

@ -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, "/")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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