mgoapi/handler.go

431 lines
9.0 KiB
Go

package mgoapi
import (
"reflect"
"strconv"
"strings"
"runtime/debug"
"gitbase.de/gopackage/mgocrud"
"github.com/gin-gonic/gin"
mgo "gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
func (api *API) collectionGetOneHandler(m mgocrud.ModelInterface) gin.HandlerFunc {
return func(c *gin.Context) {
session := api.DBSession.Copy()
defer session.Close()
db := session.DB(api.DBName)
var selector bson.M
selectorStr := c.Query("select")
if selectorStr != "" {
err := bson.UnmarshalJSON([]byte(selectorStr), &selector)
if err != nil {
c.JSON(400, errorObject(err))
return
}
}
validSelect, err := validateSelect(&Context{
API: api,
Context: c,
DB: db,
}, m, selector)
if err != nil {
c.JSON(500, errorObject(err))
return
}
newM, err := getDocument(c, db, m, validSelect)
if err != nil {
status := 500
if err == mgo.ErrNotFound {
status = 404
}
c.JSON(status, errorObject(err))
return
}
err = processResults(&Context{
API: api,
Context: c,
DB: db,
}, m, newM)
if err != nil {
c.JSON(500, errorObject(err))
return
}
c.JSON(200, newM)
}
}
func (api *API) collectionGetHandler(m mgocrud.ModelInterface) gin.HandlerFunc {
return func(c *gin.Context) {
// we need to make a slice of the correct type, so that custom MarhalJSON functions of slice elements work
// []ModelInterface type would not work => bson.M not compatible (results of mgo query)
// see: https://stackoverflow.com/questions/25384640/why-golang-reflect-makeslice-returns-un-addressable-value
slice := reflect.MakeSlice(
reflect.SliceOf(
modelTypeOf(m),
),
0, 0)
x := reflect.New(slice.Type())
x.Elem().Set(slice)
results := x.Interface()
session := api.DBSession.Copy()
defer session.Close()
db := session.DB(api.DBName)
var filter bson.M
filterStr := c.Query("filter")
if filterStr != "" {
err := bson.UnmarshalJSON([]byte(filterStr), &filter)
if err != nil {
c.JSON(400, errorObject(err))
return
}
}
validFilter, err := validateFilter(&Context{
API: api,
Context: c,
DB: db,
}, m, filter)
if err != nil {
c.JSON(500, errorObject(err))
return
}
var selector bson.M
setIdent := false
if c.Query("ident") != "" {
selector = m.GetIdentSelector()
setIdent = true
} else {
selectorStr := c.Query("select")
if selectorStr != "" {
err := bson.UnmarshalJSON([]byte(selectorStr), &selector)
if err != nil {
c.JSON(400, errorObject(err))
return
}
}
}
validSelect, err := validateSelect(&Context{
API: api,
Context: c,
DB: db,
}, m, selector)
if err != nil {
c.JSON(500, errorObject(err))
return
}
limit, _ := strconv.Atoi(c.Query("limit"))
offset, _ := strconv.Atoi(c.Query("offset"))
sort := c.QueryArray("sort")
if len(sort) <= 0 {
sort = c.QueryArray("sort[]")
}
clearedSort := []string{}
for _, s := range sort {
if s != "" {
clearedSort = append(clearedSort, s)
}
}
queryCount := c.Query("count")
if queryCount == "1" || strings.ToLower(queryCount) == "true" {
count, err := mgocrud.ReadCollectionCount(db, m, validFilter)
if err != nil {
c.JSON(500, errorObject(err))
return
}
c.Header("X-Results-Count", strconv.Itoa(count))
}
var pipelineModFunc mgocrud.PipelineModifierFunction
if i, ok := m.(interface {
PipelineModifier(*Context) (mgocrud.PipelineModifierFunction, error)
}); ok {
pipelineModFunc, err = i.PipelineModifier(&Context{
API: api,
Context: c,
DB: db,
})
if err != nil {
c.JSON(500, gin.H{
"error": err.Error(),
"stack": string(debug.Stack()),
})
return
}
}
err = mgocrud.ReadCollection(db, results, validFilter, validSelect, offset, limit, clearedSort, pipelineModFunc)
if err != nil {
c.JSON(500, errorObject(err))
return
}
err = processResults(&Context{
API: api,
Context: c,
DB: db,
}, m, results)
if err != nil {
c.JSON(500, errorObject(err))
return
}
if setIdent {
xE := x.Elem() // results slice (of pointer to slice)
for i := 0; i < xE.Len(); i++ {
resSliceEl := xE.Index(i).Addr().Interface().(mgocrud.ModelInterface)
resSliceEl.SetIdent()
}
}
c.JSON(200, results)
}
}
func (api *API) collectionPostHandler(m mgocrud.ModelInterface) gin.HandlerFunc {
return func(c *gin.Context) {
newM := newModelOf(m).(mgocrud.ModelInterface)
if err := c.Bind(newM); err != nil {
c.JSON(400, errorObject(err))
return
}
session := api.DBSession.Copy()
defer session.Close()
db := session.DB(api.DBName)
if err := validateObject(&Context{
API: api,
Context: c,
DB: db,
}, newM, nil); err != nil {
c.JSON(400, errorObject(err))
return
}
err := mgocrud.CreateDocument(db, newM)
if err != nil {
c.JSON(500, errorObject(err))
return
}
readM := newModelOf(newM).(mgocrud.ModelInterface)
readM.SetID(newM.GetID())
err = mgocrud.ReadDocument(db, readM, nil)
if err != nil {
c.JSON(500, errorObject(err))
return
}
if err := savedObject(&Context{
API: api,
Context: c,
DB: db,
}, readM, nil); err != nil {
c.JSON(400, errorObject(err))
return
}
err = processResults(&Context{
API: api,
Context: c,
DB: db,
}, m, readM)
if err != nil {
c.JSON(500, errorObject(err))
return
}
c.JSON(200, readM)
}
}
func (api *API) collectionPutHandler(m mgocrud.ModelInterface) gin.HandlerFunc {
return func(c *gin.Context) {
session := api.DBSession.Copy()
defer session.Close()
db := session.DB(api.DBName)
orgM, err := getDocument(c, db, m, nil)
if err != nil {
status := 500
if err == mgo.ErrNotFound {
status = 404
}
c.JSON(status, errorObject(err))
return
}
newM := newModelOf(m).(mgocrud.ModelInterface)
if err := c.Bind(newM); err != nil {
c.JSON(400, errorObject(err))
return
}
// dont allow id in PUT
if newM.GetID() != nil {
c.JSON(400, gin.H{
"error": "id not allowed in update",
})
return
}
/*
mapDecoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
ErrorUnused: true,
ZeroFields: true,
Result: orgM,
TagName: "json",
})
if err != nil {
c.JSON(500, gin.H{
"error": err.Error(),
"stack": string(debug.Stack()),
})
return
}
err = mapDecoder.Decode(change)
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
"stack": string(debug.Stack()),
})
return
}
*/
// apply newM on orgM and record changes for db update, only first level for now
changes := bson.M{}
newMStruct := reflect.Indirect(reflect.ValueOf(newM))
orgMStruct := reflect.Indirect(reflect.ValueOf(orgM))
for i := 0; i < newMStruct.NumField(); i++ {
field := newMStruct.Field(i)
fieldT := newMStruct.Type().Field(i)
if fieldT.Anonymous && fieldT.Name == "Model" { // is embedded mgocrud.Model, so not in changes
continue
}
nilAble := (field.Kind() == reflect.Ptr || field.Kind() == reflect.Map || field.Kind() == reflect.Slice)
if (nilAble && !field.IsNil()) || !nilAble {
// apply naming conventions of mgo for changeset
tag := fieldT.Tag.Get("bson")
if tag == "" && !strings.Contains(string(fieldT.Tag), ":") {
tag = string(fieldT.Tag)
}
if tag == "-" {
continue
}
tagFields := strings.Split(tag, ",")
tag = tagFields[0]
fieldName := tag
if fieldName == "" {
fieldName = strings.ToLower(fieldT.Name)
}
changes[fieldName] = field.Interface()
orgMStruct.Field(i).Set(field)
}
}
//spew.Dump(changes)
//spew.Dump(orgM)
if err := validateObject(&Context{
API: api,
Context: c,
DB: db,
}, orgM, changes); err != nil {
c.JSON(400, errorObject(err))
return
}
err = mgocrud.UpdateDocument(db, orgM, changes)
if err != nil {
c.JSON(500, errorObject(err))
return
}
//spew.Dump(orgM)
// reread from db
newM, err = getDocument(c, db, orgM, nil)
if err != nil {
c.JSON(500, errorObject(err))
return
}
if err := savedObject(&Context{
API: api,
Context: c,
DB: db,
}, newM, changes); err != nil {
c.JSON(400, errorObject(err))
return
}
err = processResults(&Context{
API: api,
Context: c,
DB: db,
}, m, newM)
if err != nil {
c.JSON(500, errorObject(err))
return
}
c.JSON(200, newM)
}
}
func (api *API) collectionDeleteHandler(m mgocrud.ModelInterface) gin.HandlerFunc {
return func(c *gin.Context) {
session := api.DBSession.Copy()
defer session.Close()
db := session.DB(api.DBName)
orgM, err := getDocument(c, db, m, bson.M{"_id": 1})
if err != nil {
status := 500
if err == mgo.ErrNotFound {
status = 404
}
c.JSON(status, errorObject(err))
return
}
err = mgocrud.DeleteDocument(db, orgM)
if err != nil {
c.JSON(500, errorObject(err))
}
c.JSON(200, gin.H{"message": "ok"})
}
}
func (api *API) collectionGetMetaHandler(m mgocrud.ModelInterface) gin.HandlerFunc {
return func(c *gin.Context) {
c.JSON(200, getModelMeta(m))
}
}