impl entities except for PATCH
parent
1655a9b83a
commit
f88ade0d73
4
main.go
4
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, "/")
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue