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/config"
|
||||||
"local/dndex/server"
|
"local/dndex/server"
|
||||||
"local/dndex/storage"
|
"local/dndex/storage"
|
||||||
"local/dndex/view"
|
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -14,9 +13,8 @@ func main() {
|
||||||
c := config.New()
|
c := config.New()
|
||||||
log.Println(c)
|
log.Println(c)
|
||||||
g := storage.NewRateLimitedGraph()
|
g := storage.NewRateLimitedGraph()
|
||||||
view.GitCommit = GitCommit
|
|
||||||
server.GitCommit = GitCommit
|
server.GitCommit = GitCommit
|
||||||
if err := view.JSON(g); err != nil {
|
if err := server.Listen(g); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ func (s *Scope) fromToken(r *http.Request) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scope) fromPath(path string) bool {
|
func (s *Scope) fromPath(path string) bool {
|
||||||
if !strings.HasPrefix(path, "/entity/") {
|
if !strings.HasPrefix(path, "/entities/") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
paths := strings.Split(path, "/")
|
paths := strings.Split(path, "/")
|
||||||
|
|
|
||||||
|
|
@ -56,29 +56,29 @@ func TestScopeFromPath(t *testing.T) {
|
||||||
ok bool
|
ok bool
|
||||||
id string
|
id string
|
||||||
}{
|
}{
|
||||||
"/": {},
|
"/": {},
|
||||||
"/hello": {},
|
"/hello": {},
|
||||||
"/hello/": {},
|
"/hello/": {},
|
||||||
"/hello/entity": {},
|
"/hello/entities": {},
|
||||||
"/entity": {},
|
"/entities": {},
|
||||||
"/entity/": {},
|
"/entities/": {},
|
||||||
"/entity/id": {
|
"/entities/id": {
|
||||||
ok: true,
|
ok: true,
|
||||||
id: "id",
|
id: "id",
|
||||||
},
|
},
|
||||||
"/entity/id/": {
|
"/entities/id/": {
|
||||||
ok: true,
|
ok: true,
|
||||||
id: "id",
|
id: "id",
|
||||||
},
|
},
|
||||||
"/entity/id/excess": {
|
"/entities/id/excess": {
|
||||||
ok: true,
|
ok: true,
|
||||||
id: "id",
|
id: "id",
|
||||||
},
|
},
|
||||||
"/entity/id#excess": {
|
"/entities/id#excess": {
|
||||||
ok: true,
|
ok: true,
|
||||||
id: "id",
|
id: "id",
|
||||||
},
|
},
|
||||||
"/entity/id?excess": {
|
"/entities/id?excess": {
|
||||||
ok: true,
|
ok: true,
|
||||||
id: "id",
|
id: "id",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,31 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"local/dndex/storage/entity"
|
||||||
|
"local/dndex/storage/operator"
|
||||||
|
"log"
|
||||||
"net/http"
|
"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) {
|
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 {
|
switch r.Method {
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
rest.entitiesReplace(w, r)
|
rest.entitiesReplace(w, r)
|
||||||
|
|
@ -17,26 +38,156 @@ func (rest *REST) entities(w http.ResponseWriter, r *http.Request) {
|
||||||
case http.MethodDelete:
|
case http.MethodDelete:
|
||||||
rest.entitiesDelete(w, r)
|
rest.entitiesDelete(w, r)
|
||||||
default:
|
default:
|
||||||
http.NotFound(w, r)
|
rest.respNotFound(w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rest *REST) entitiesCreate(w http.ResponseWriter, r *http.Request) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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)
|
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) {
|
func (rest *REST) files(w http.ResponseWriter, r *http.Request) {
|
||||||
if len(r.URL.Path) < 2 {
|
if len(r.URL.Path) < 2 {
|
||||||
http.NotFound(w, r)
|
rest.respNotFound(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
|
|
@ -23,7 +23,7 @@ func (rest *REST) files(w http.ResponseWriter, r *http.Request) {
|
||||||
case http.MethodDelete:
|
case http.MethodDelete:
|
||||||
rest.filesDelete(w, r)
|
rest.filesDelete(w, r)
|
||||||
default:
|
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) {
|
func (rest *REST) filesGet(w http.ResponseWriter, r *http.Request) {
|
||||||
localPath := rest.filesPath(r)
|
localPath := rest.filesPath(r)
|
||||||
if stat, err := os.Stat(localPath); os.IsNotExist(err) {
|
if stat, err := os.Stat(localPath); os.IsNotExist(err) {
|
||||||
http.NotFound(w, r)
|
rest.respNotFound(w)
|
||||||
return
|
return
|
||||||
} else if err != nil || stat.IsDir() {
|
} else if err != nil || stat.IsDir() {
|
||||||
rest.respConflict(w)
|
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) {
|
func (rest *REST) filesUpdate(w http.ResponseWriter, r *http.Request) {
|
||||||
localPath := rest.filesPath(r)
|
localPath := rest.filesPath(r)
|
||||||
if stat, err := os.Stat(localPath); os.IsNotExist(err) {
|
if stat, err := os.Stat(localPath); os.IsNotExist(err) {
|
||||||
http.NotFound(w, r)
|
rest.respNotFound(w)
|
||||||
return
|
return
|
||||||
} else if err != nil || stat.IsDir() {
|
} else if err != nil || stat.IsDir() {
|
||||||
rest.respConflict(w)
|
rest.respConflict(w)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,13 @@ import (
|
||||||
"time"
|
"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 {
|
func (rest *REST) delay(foo http.HandlerFunc) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
select {
|
select {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@ func (rest *REST) respOK(w http.ResponseWriter) {
|
||||||
rest.respMap(w, "ok", true)
|
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) {
|
func (rest *REST) respConflict(w http.ResponseWriter) {
|
||||||
rest.respMapStatus(w, "error", "collision found", http.StatusConflict)
|
rest.respMapStatus(w, "error", "collision found", http.StatusConflict)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"local/dndex/storage"
|
"local/dndex/storage"
|
||||||
"local/router"
|
"local/router"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type REST struct {
|
type REST struct {
|
||||||
|
|
@ -43,7 +44,10 @@ func NewREST(g storage.RateLimitedGraph) (*REST, error) {
|
||||||
for path, foo := range paths {
|
for path, foo := range paths {
|
||||||
bar := foo
|
bar := foo
|
||||||
bar = rest.shift(bar)
|
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.defend(bar)
|
||||||
bar = rest.delay(bar)
|
bar = rest.delay(bar)
|
||||||
if err := rest.router.Add(path, bar); err != nil {
|
if err := rest.router.Add(path, bar); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -114,19 +114,15 @@ func TestRESTRouter(t *testing.T) {
|
||||||
},
|
},
|
||||||
fmt.Sprintf("/entities/%s/connections/", testEntityID): {
|
fmt.Sprintf("/entities/%s/connections/", testEntityID): {
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
is404: true,
|
|
||||||
},
|
},
|
||||||
fmt.Sprintf("/entities/%s/connections/", testEntityID): {
|
fmt.Sprintf("/entities/%s/connections/", testEntityID): {
|
||||||
method: http.MethodPut,
|
method: http.MethodPut,
|
||||||
is404: true,
|
|
||||||
},
|
},
|
||||||
fmt.Sprintf("/entities/%s/connections/", testEntityID): {
|
fmt.Sprintf("/entities/%s/connections/", testEntityID): {
|
||||||
method: http.MethodPatch,
|
method: http.MethodPatch,
|
||||||
is404: true,
|
|
||||||
},
|
},
|
||||||
fmt.Sprintf("/entities/%s/connections", testEntityID): {
|
fmt.Sprintf("/entities/%s/connections", testEntityID): {
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
is404: true,
|
|
||||||
},
|
},
|
||||||
fmt.Sprintf("/entities/%s/connections", testEntityID): {
|
fmt.Sprintf("/entities/%s/connections", testEntityID): {
|
||||||
method: http.MethodPut,
|
method: http.MethodPut,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ func (rest *REST) users(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
default:
|
default:
|
||||||
http.NotFound(w, r)
|
rest.respNotFound(w)
|
||||||
}
|
}
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
case "/register":
|
case "/register":
|
||||||
|
|
@ -17,7 +17,7 @@ func (rest *REST) users(w http.ResponseWriter, r *http.Request) {
|
||||||
case "/login":
|
case "/login":
|
||||||
rest.usersLogin(w, r)
|
rest.usersLogin(w, r)
|
||||||
default:
|
default:
|
||||||
http.NotFound(w, r)
|
rest.respNotFound(w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -224,8 +224,10 @@ func matches(doc, filter bson.M) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if fmt.Sprint(v) != fmt.Sprint(doc[k]) {
|
if fmt.Sprint(v) != "nil" && fmt.Sprint(v) != "" {
|
||||||
return false
|
if fmt.Sprint(v) != fmt.Sprint(doc[k]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue