impl entities except for PATCH
This commit is contained in:
355
.view/who.go
Normal file
355
.view/who.go
Normal file
@@ -0,0 +1,355 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"local/dndex/config"
|
||||
"local/dndex/storage"
|
||||
"local/dndex/storage/entity"
|
||||
"local/dndex/storage/operator"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/gomarkdown/markdown/html"
|
||||
"github.com/gomarkdown/markdown/parser"
|
||||
"github.com/google/uuid"
|
||||
"github.com/iancoleman/orderedmap"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
const (
|
||||
querySort = "sort"
|
||||
queryOrder = "order"
|
||||
)
|
||||
|
||||
func who(g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error {
|
||||
namespace, err := getNamespace(r)
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
return whoGet(namespace, g, w, r)
|
||||
case http.MethodPut:
|
||||
return whoPut(namespace, g, w, r)
|
||||
case http.MethodPost:
|
||||
return whoPost(namespace, g, w, r)
|
||||
case http.MethodDelete:
|
||||
return whoDelete(namespace, g, w, r)
|
||||
case http.MethodPatch:
|
||||
return whoPatch(namespace, g, w, r)
|
||||
case http.MethodTrace:
|
||||
return whoTrace(namespace, g, w, r)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func whoGet(namespace string, g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error {
|
||||
id, err := getCleanID(r)
|
||||
if err != nil {
|
||||
return whoTrace(namespace, g, w, r)
|
||||
}
|
||||
_, light := r.URL.Query()["light"]
|
||||
_, md := r.URL.Query()["md"]
|
||||
|
||||
ones, err := g.ListCaseInsensitive(r.Context(), namespace, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ones) == 0 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return json.NewEncoder(w).Encode(entity.One{})
|
||||
}
|
||||
if len(ones) > 1 {
|
||||
return fmt.Errorf("more than one result found matching %q: %+v", id, ones)
|
||||
}
|
||||
one := ones[0]
|
||||
|
||||
baseUrl := *r.URL
|
||||
if baseUrl.Scheme == "" {
|
||||
baseUrl.Scheme = "http"
|
||||
}
|
||||
if baseUrl.Host == "" {
|
||||
baseUrl.Host = r.Host
|
||||
}
|
||||
baseUrl.Path = ""
|
||||
baseUrl.RawQuery = ""
|
||||
for k := range one.Attachments {
|
||||
if _, err := url.Parse(one.Attachments[k]); err != nil || !strings.Contains(one.Attachments[k], ":") {
|
||||
one.Attachments[k] = baseUrl.String() + path.Join("/", config.New().FilePrefix, namespace, one.Attachments[k])
|
||||
}
|
||||
}
|
||||
|
||||
if !light && len(one.Connections) > 0 {
|
||||
ones, err := g.ListCaseInsensitive(r.Context(), namespace, one.Peers()...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, another := range ones {
|
||||
another.Relationship = one.Connections[another.Name].Relationship
|
||||
one.Connections[another.Name] = another
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(one, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := r.URL.Query()[querySort]; ok {
|
||||
m := bson.M{}
|
||||
if err := json.Unmarshal(b, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
ones := make([]entity.One, len(one.Connections))
|
||||
i := 0
|
||||
for _, v := range one.Connections {
|
||||
ones[i] = v
|
||||
i++
|
||||
}
|
||||
m[entity.Connections] = sortOnesObject(ones, r)
|
||||
b, err = json.MarshalIndent(m, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if md {
|
||||
m := bson.M{}
|
||||
if err := json.Unmarshal(b, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
renderer := html.NewRenderer(html.RendererOptions{Flags: html.CommonFlags | html.TOC})
|
||||
parser := parser.NewWithExtensions(parser.CommonExtensions | parser.HeadingIDs | parser.AutoHeadingIDs | parser.Titleblock)
|
||||
m["md"] = markdownHead + string(markdown.ToHTML([]byte(one.Text), parser, renderer)) + markdownTail
|
||||
|
||||
b, err = json.MarshalIndent(m, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = io.Copy(w, bytes.NewReader(b))
|
||||
return err
|
||||
}
|
||||
|
||||
func whoPut(namespace string, g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error {
|
||||
id, err := getID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reader := bytes.NewReader(body)
|
||||
|
||||
operation := entity.One{}
|
||||
if err := json.NewDecoder(reader).Decode(&operation); err != nil {
|
||||
return err
|
||||
}
|
||||
if operation.Name != "" && operation.Name != id {
|
||||
http.Error(w, `{"error":"names differ between URL and request body"}`, http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
if operation.Modified != 0 {
|
||||
http.Error(w, `{"error":"cannot specify modified in request body"}`, http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
op := bson.M{}
|
||||
if err := json.Unmarshal(body, &op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := g.Update(r.Context(), namespace, entity.One{Name: id}, operator.SetMany{Value: op}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return whoGet(namespace, g, w, r)
|
||||
}
|
||||
|
||||
func whoPost(namespace string, g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error {
|
||||
id, err := getID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
one := entity.One{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&one); err != nil {
|
||||
return err
|
||||
}
|
||||
if one.Name != "" && one.Name != id {
|
||||
http.Error(w, `{"error":"names differ between URL and request body"}`, http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
one.Name = id
|
||||
if one.ID != "" {
|
||||
http.Error(w, `{"error":"cannot specify ID in body"}`, http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
one.ID = uuid.New().String()
|
||||
if err := g.Insert(r.Context(), namespace, one); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return whoGet(namespace, g, w, r)
|
||||
}
|
||||
|
||||
func whoDelete(namespace string, g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error {
|
||||
id, err := getCleanID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
_, ok := r.URL.Query()["connection"]
|
||||
if ok {
|
||||
return whoDeleteConnections(namespace, g, w, r)
|
||||
}
|
||||
|
||||
if err := g.Delete(r.Context(), namespace, operator.CaseInsensitive{Key: entity.Name, Value: id}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func whoDeleteConnections(namespace string, g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error {
|
||||
id, err := getID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
connections, ok := r.URL.Query()["connection"]
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return json.NewEncoder(w).Encode(map[string]string{"error": "must provide connections to delete"})
|
||||
}
|
||||
|
||||
one := entity.One{Name: id}
|
||||
|
||||
for _, connection := range connections {
|
||||
path := fmt.Sprintf("%s.%s", entity.Connections, connection)
|
||||
if err := g.Update(r.Context(), namespace, one.Query(), operator.Unset(path)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return whoGet(namespace, g, w, r)
|
||||
}
|
||||
|
||||
func whoPatch(namespace string, g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error {
|
||||
id, err := getID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
one := entity.One{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&one); err != nil {
|
||||
return err
|
||||
}
|
||||
if one.Name == "" {
|
||||
http.Error(w, `{"error":"no name provided"}`, http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
relationship := one.Relationship
|
||||
one.Relationship = ""
|
||||
if err := g.Insert(r.Context(), namespace, one); err != nil && !strings.Contains(err.Error(), "ollision") {
|
||||
return err
|
||||
}
|
||||
one.Relationship = relationship
|
||||
if err := g.Update(r.Context(), namespace, entity.One{Name: id}, operator.Set{Key: fmt.Sprintf("%s.%s", entity.Connections, one.Name), Value: one.Peer()}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return whoGet(namespace, g, w, r)
|
||||
}
|
||||
|
||||
func whoTrace(namespace string, g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error {
|
||||
ones, err := g.ListCaseInsensitive(r.Context(), namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ones = sortOnes(ones, r)
|
||||
names := make([]string, len(ones))
|
||||
for i := range ones {
|
||||
names[i] = ones[i].Name
|
||||
}
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(names)
|
||||
}
|
||||
|
||||
func getCleanID(r *http.Request) (string, error) {
|
||||
id, err := getID(r)
|
||||
return sanitize(id), err
|
||||
}
|
||||
|
||||
func getID(r *http.Request) (string, error) {
|
||||
id := r.URL.Query().Get("id")
|
||||
if id == "" {
|
||||
return "", errors.New("no id provided")
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func sortOnesObject(ones []entity.One, r *http.Request) interface{} {
|
||||
ones = sortOnes(ones, r)
|
||||
m := orderedmap.New()
|
||||
for _, one := range ones {
|
||||
m.Set(one.Name, one)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func sortOnes(ones []entity.One, r *http.Request) []entity.One {
|
||||
sorting := sanitize(r.URL.Query().Get(querySort))
|
||||
if sorting == "" {
|
||||
sorting = entity.Modified
|
||||
}
|
||||
order := sanitize(r.URL.Query().Get(queryOrder))
|
||||
if order == "" {
|
||||
order = "-1"
|
||||
}
|
||||
asc := order != "-1"
|
||||
sort.Slice(ones, func(i, j int) bool {
|
||||
if sorting == entity.Name {
|
||||
return (asc && ones[i].Name < ones[j].Name) || (!asc && ones[i].Name > ones[j].Name)
|
||||
}
|
||||
ib, _ := json.Marshal(ones[i])
|
||||
jb, _ := json.Marshal(ones[j])
|
||||
var im, jm bson.M
|
||||
json.Unmarshal(ib, &im)
|
||||
json.Unmarshal(jb, &jm)
|
||||
iv, _ := im[sorting]
|
||||
jv, _ := jm[sorting]
|
||||
is := fmt.Sprint(iv)
|
||||
js := fmt.Sprint(jv)
|
||||
return (asc && is < js) || (!asc && is > js)
|
||||
})
|
||||
return ones
|
||||
}
|
||||
|
||||
func sanitize(s string) string {
|
||||
re := regexp.MustCompile(`[^a-zA-Z0-9- _]`)
|
||||
s = re.ReplaceAllString(s, `.`)
|
||||
return s
|
||||
}
|
||||
Reference in New Issue
Block a user