dndex/server/entities.go

270 lines
6.2 KiB
Go

package server
import (
"encoding/json"
"errors"
"io"
"local/dndex/storage/entity"
"local/dndex/storage/operator"
"net/http"
"path"
"strings"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
"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
}
_, md := r.URL.Query()["md"]
if md {
renderer := html.NewRenderer(html.RendererOptions{Flags: html.CommonFlags | html.TOC})
parser := parser.NewWithExtensions(parser.CommonExtensions | parser.HeadingIDs | parser.AutoHeadingIDs | parser.Titleblock)
resp["md"] = markdownHead + string(markdown.ToHTML([]byte(one.Text), parser, renderer)) + markdownTail
}
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
}