package server import ( "encoding/json" "errors" "io" "local/dndex/storage/entity" "local/dndex/storage/operator" "net/http" "path" "strings" "github.com/google/uuid" "go.mongodb.org/mongo-driver/bson/primitive" "gopkg.in/mgo.v2/bson" ) type shortEntity struct { Name string ID string } func (rest *REST) entities(w http.ResponseWriter, r *http.Request) { scope, err := rest.entityScope(r) if err != nil { rest.respNotFound(w) return } if r.Method == http.MethodGet { } else if r.Method == http.MethodPost && len(scope) != 0 { rest.respNotFound(w) return } else if len(scope) > 1 { if r.Method == http.MethodDelete { q := r.URL.Query() q.Set("delete", "") r.URL.RawQuery = q.Encode() } r.Method = http.MethodPatch } if r.Method == http.MethodPatch && len(scope) == 1 { r.Method = http.MethodPut } if r.Method == http.MethodPatch && len(scope) == 0 { rest.respNotFound(w) return } switch r.Method { case http.MethodPut: rest.entitiesReplace(w, r) case http.MethodPatch: rest.entitiesUpdate(w, r) case http.MethodPost: rest.entitiesCreate(w, r) case http.MethodGet: rest.entitiesGet(w, r) case http.MethodDelete: rest.entitiesDelete(w, r) default: rest.respNotFound(w) } } func (rest *REST) entitiesCreate(w http.ResponseWriter, r *http.Request) { scope := rest.scope(r) one, err := rest.entityParse(r.Body) if err != nil { rest.respBadRequest(w, err.Error()) return } one.ID = uuid.New().String() if err := rest.g.Insert(r.Context(), scope.Namespace, one); err != nil { rest.respError(w, err) return } r.URL.Path += "/" + one.ID rest.entitiesGet(w, r) } func (rest *REST) entitiesDelete(w http.ResponseWriter, r *http.Request) { 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) { entityScope, _ := rest.entityScope(r) switch len(entityScope) { case 0: rest.entitiesGetN(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 } resp := one.Generic() if _, ok := r.URL.Query()["light"]; !ok { m := bson.M{} for id := range one.Connections { one2, err := rest.g.Get(r.Context(), scope.Namespace, id) if err == nil { m2 := one2.Generic() m2[entity.Relationship] = one.Connections[id].Relationship m[id] = m2 } } resp[entity.Connections] = m } rest.respMap(w, entityScope[0], resp) } 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) entitiesGetN(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) { 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) { entityScope, _ := rest.entityScope(r) scope := rest.scope(r) _, del := r.URL.Query()["delete"] var m interface{} if !del { err := json.NewDecoder(r.Body).Decode(&m) if err != nil { rest.respBadRequest(w, err.Error()) return } if mm, ok := m.(primitive.M); ok { delete(mm, entity.ID) m = mm } } key := strings.Join(entityScope[1:], ".") var operation interface{} operation = operator.Set{Key: key, Value: m} if del { operation = operator.Unset(key) } err := rest.g.Update( r.Context(), scope.Namespace, bson.M{entity.ID: scope.EntityID}, operation, ) if err != nil { rest.respError(w, err) return } r.URL.Path = path.Join("/", scope.EntityID) rest.entitiesGet(w, r) } 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 || r.Method == http.MethodPost { 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 }