Initial commit
This commit is contained in:
307
filter.go
Normal file
307
filter.go
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
package mgoapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.basehosts.de/gopackage/mgocrud"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func structFieldFromJSON(t reflect.Type, fieldname string) (sfield string, err error) {
|
||||||
|
if t.Kind() == reflect.Slice {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
return "", fmt.Errorf("cannot find field from json name %s, %+v is no struct", fieldname, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
if f.Anonymous {
|
||||||
|
// embedded struct
|
||||||
|
embedSField, _ := structFieldFromJSON(f.Type, fieldname)
|
||||||
|
if embedSField != "" {
|
||||||
|
return embedSField, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonTag := strings.Split(f.Tag.Get("json"), ",")
|
||||||
|
if jsonTag[0] == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if jsonTag[0] == fieldname {
|
||||||
|
return f.Name, nil
|
||||||
|
}
|
||||||
|
if jsonTag[0] == "" && f.Name == fieldname {
|
||||||
|
return f.Name, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("json field %s not found in %+v", fieldname, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bsonFieldFromStruct(t reflect.Type, fieldname string) (bfield string, err error) {
|
||||||
|
if t.Kind() == reflect.Slice {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
return "", fmt.Errorf("cannot find struct field from bson name %s, %+v is no struct", fieldname, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f, found := t.FieldByName(fieldname); found {
|
||||||
|
bsonTag := strings.Split(f.Tag.Get("bson"), ",")
|
||||||
|
if bsonTag[0] == "-" {
|
||||||
|
return "", fmt.Errorf("struct field %s from %+v is no bson field", fieldname, t)
|
||||||
|
}
|
||||||
|
if bsonTag[0] != "" {
|
||||||
|
return bsonTag[0], nil
|
||||||
|
}
|
||||||
|
return strings.ToLower(fieldname), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("struct field %s not found in %+v", fieldname, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func typeOfStructFieldByName(t reflect.Type, fieldname string) (reflect.Type, error) {
|
||||||
|
if t.Kind() == reflect.Slice {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
return nil, fmt.Errorf("cannot get field type of %s, %+v is no struct", fieldname, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f, found := t.FieldByName(fieldname); found {
|
||||||
|
fT := f.Type
|
||||||
|
if fT.Kind() == reflect.Ptr {
|
||||||
|
return fT.Elem(), nil
|
||||||
|
}
|
||||||
|
return fT, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("struct field %s not found in %+v", fieldname, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _checkValue(fieldT reflect.Type, val interface{}) (newVal interface{}, err error) {
|
||||||
|
fieldTE := fieldT
|
||||||
|
if fieldTE.Kind() == reflect.Ptr || fieldTE.Kind() == reflect.Slice {
|
||||||
|
fieldTE = fieldTE.Elem()
|
||||||
|
}
|
||||||
|
if fieldTE.Kind() == reflect.Ptr {
|
||||||
|
fieldTE = fieldTE.Elem()
|
||||||
|
}
|
||||||
|
switch value := val.(type) {
|
||||||
|
|
||||||
|
case string:
|
||||||
|
switch fieldTE {
|
||||||
|
case reflect.TypeOf(bson.ObjectId("")): // struct field is objectid
|
||||||
|
newVal = bson.ObjectIdHex(value)
|
||||||
|
case reflect.TypeOf(time.Now()): // struct field is time
|
||||||
|
newVal, err = time.Parse(time.RFC3339, value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("date string from filter is not compatible with type %s (date must be RFC3339)", fieldT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
switch fieldTE {
|
||||||
|
case reflect.TypeOf(int(0)): // json is always float64 for numbers
|
||||||
|
newVal = int(value)
|
||||||
|
}
|
||||||
|
case bson.RegEx:
|
||||||
|
switch fieldTE {
|
||||||
|
case reflect.TypeOf(""):
|
||||||
|
// leave as is
|
||||||
|
newVal = value
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if newVal == nil {
|
||||||
|
valT := reflect.TypeOf(val)
|
||||||
|
if fieldTE == valT {
|
||||||
|
newVal = val
|
||||||
|
} else if valT.Kind() == reflect.Map {
|
||||||
|
// go into sub object and validate
|
||||||
|
newVal, err = _validateFilter(fieldTE, val.(map[string]interface{}), true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("type %s from filter is not compatible with type %s", valT, fieldT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newVal, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func _validateFilter(structT reflect.Type, f bson.M, checkValues bool) (bson.M, error) {
|
||||||
|
newF := make(bson.M)
|
||||||
|
for key, val := range f {
|
||||||
|
if key == "" {
|
||||||
|
return nil, fmt.Errorf("empty key in filter for model %+v", structT)
|
||||||
|
}
|
||||||
|
newKey := ""
|
||||||
|
var newVal interface{}
|
||||||
|
|
||||||
|
if key == "$text" {
|
||||||
|
// dont modify or check search
|
||||||
|
newKey = key
|
||||||
|
newVal = val
|
||||||
|
} else if strings.HasPrefix(key, "$") {
|
||||||
|
// is filter function and no fieldname
|
||||||
|
newKey = key
|
||||||
|
valT := reflect.TypeOf(val)
|
||||||
|
if valT.Kind() == reflect.Slice {
|
||||||
|
l := len(val.([]interface{}))
|
||||||
|
newVal = make([]interface{}, l, l)
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
var err error
|
||||||
|
switch valF := val.([]interface{})[i].(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
// array element is object
|
||||||
|
newVal.([]interface{})[i], err = _validateFilter(structT, valF, checkValues)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// array element is value
|
||||||
|
newVal.([]interface{})[i], err = _checkValue(structT, valF)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s from %+v for field %s", err.Error(), structT, newKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
switch valF := val.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
newVal, err = _validateFilter(structT, valF, checkValues)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
newVal, err = _checkValue(structT, val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s from %+v for field %s", err.Error(), structT, newKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
keys := strings.Split(key, ".")
|
||||||
|
|
||||||
|
var _getBSONField func(k []string, t reflect.Type) error
|
||||||
|
_getBSONField = func(k []string, t reflect.Type) error {
|
||||||
|
structField, err := structFieldFromJSON(t, k[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bsonField, err := bsonFieldFromStruct(t, structField)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newKey += bsonField
|
||||||
|
|
||||||
|
fieldT, err := typeOfStructFieldByName(t, structField)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(k) > 1 {
|
||||||
|
newKey += "."
|
||||||
|
return _getBSONField(k[1:], fieldT)
|
||||||
|
}
|
||||||
|
|
||||||
|
// last element -> check value
|
||||||
|
if checkValues {
|
||||||
|
newVal, err = _checkValue(fieldT, val)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s from %+v for field %s", err.Error(), t, newKey)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newVal = val
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := _getBSONField(keys, structT)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newF[newKey] = newVal
|
||||||
|
|
||||||
|
// spew.Dump(key)
|
||||||
|
// spew.Dump(val)
|
||||||
|
// spew.Dump(newKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newF, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateFilter(c *Context, m mgocrud.ModelInterface, filter bson.M) (newFilter bson.M, err error) {
|
||||||
|
// spew.Dump(filter)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("filter validation error: %s", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if filter != nil {
|
||||||
|
newFilter, err = _validateFilter(reflect.TypeOf(m), filter, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i, ok := m.(interface {
|
||||||
|
ValidateFilter(*Context, bson.M) (bson.M, error)
|
||||||
|
}); ok {
|
||||||
|
// custom filter manipulation
|
||||||
|
newFilter, err = i.ValidateFilter(c, newFilter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// spew.Dump(newFilter)
|
||||||
|
|
||||||
|
return newFilter, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSelect(c *Context, m mgocrud.ModelInterface, selector bson.M) (newSelector bson.M, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("select validation error: %s", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if selector != nil {
|
||||||
|
newSelector, err = _validateFilter(reflect.TypeOf(m), selector, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i, ok := m.(interface {
|
||||||
|
ValidateSelect(*Context, bson.M) bson.M
|
||||||
|
}); ok {
|
||||||
|
// custom select manipulation
|
||||||
|
newSelector = i.ValidateSelect(c, newSelector)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSelector, nil
|
||||||
|
}
|
||||||
417
handler.go
Normal file
417
handler.go
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
package mgoapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.basehosts.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, gin.H{
|
||||||
|
"error": "select: " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validSelect, err := validateSelect(&Context{
|
||||||
|
API: api,
|
||||||
|
Context: c,
|
||||||
|
DB: db,
|
||||||
|
}, m, selector)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"error": "select: " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newM, err := getDocument(c, db, m, validSelect)
|
||||||
|
if err != nil {
|
||||||
|
status := 500
|
||||||
|
if err == mgo.ErrNotFound {
|
||||||
|
status = 404
|
||||||
|
}
|
||||||
|
c.JSON(status, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if i, ok := m.(interface {
|
||||||
|
ProcessResults(*Context, interface{}) error
|
||||||
|
}); ok {
|
||||||
|
// custom select manipulation
|
||||||
|
err := i.ProcessResults(&Context{
|
||||||
|
API: api,
|
||||||
|
Context: c,
|
||||||
|
DB: db,
|
||||||
|
}, newM)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
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, gin.H{
|
||||||
|
"error": "filter: " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validFilter, err := validateFilter(&Context{
|
||||||
|
API: api,
|
||||||
|
Context: c,
|
||||||
|
DB: db,
|
||||||
|
}, m, filter)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"error": "filter: " + err.Error(),
|
||||||
|
})
|
||||||
|
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, gin.H{
|
||||||
|
"error": "select: " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validSelect, err := validateSelect(&Context{
|
||||||
|
API: api,
|
||||||
|
Context: c,
|
||||||
|
DB: db,
|
||||||
|
}, m, selector)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"error": "select: " + err.Error(),
|
||||||
|
})
|
||||||
|
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, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
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(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mgocrud.ReadCollection(db, results, validFilter, validSelect, offset, limit, clearedSort, pipelineModFunc)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if i, ok := m.(interface {
|
||||||
|
ProcessResults(*Context, interface{}) error
|
||||||
|
}); ok {
|
||||||
|
// custom select manipulation
|
||||||
|
err := i.ProcessResults(&Context{
|
||||||
|
API: api,
|
||||||
|
Context: c,
|
||||||
|
DB: db,
|
||||||
|
}, results)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
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, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session := api.DBSession.Copy()
|
||||||
|
defer session.Close()
|
||||||
|
db := session.DB(api.DBName)
|
||||||
|
|
||||||
|
if err := mgocrud.ValidateObject(db, newM, nil); err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mgocrud.CreateDocument(db, newM)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, newM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newM := newModelOf(m).(mgocrud.ModelInterface)
|
||||||
|
|
||||||
|
if err := c.Bind(newM); err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
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(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mapDecoder.Decode(change)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// apply newM on orgM and record changes for db update
|
||||||
|
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 field.Kind() == reflect.Ptr && !field.IsNil() {
|
||||||
|
// 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 := mgocrud.ValidateObject(db, orgM, changes); err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//spew.Dump(orgM)
|
||||||
|
err = mgocrud.UpdateDocument(db, orgM, changes)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//spew.Dump(orgM)
|
||||||
|
// reread from db
|
||||||
|
newM, err = getDocument(c, db, orgM, nil)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
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, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mgocrud.DeleteDocument(db, orgM)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
164
helper.go
Normal file
164
helper.go
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package mgoapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.basehosts.de/gopackage/mgocrud"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
mgo "gopkg.in/mgo.v2"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func modelTypeOf(m interface{}) reflect.Type {
|
||||||
|
return reflect.TypeOf(
|
||||||
|
reflect.ValueOf(m).Elem().Interface(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newModelOf(m interface{}) interface{} {
|
||||||
|
return reflect.New(
|
||||||
|
modelTypeOf(m),
|
||||||
|
).Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
func string2ObjectID(id string) (objectID *bson.ObjectId, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
switch x := r.(type) {
|
||||||
|
case string:
|
||||||
|
err = errors.New(x)
|
||||||
|
case error:
|
||||||
|
err = x
|
||||||
|
default:
|
||||||
|
err = errors.New("Unknown panic in: string2ObjectID")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
oID := bson.ObjectIdHex(id)
|
||||||
|
return &oID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStructMeta(s reflect.Type, dontRecurse map[string]bool) []map[string]interface{} {
|
||||||
|
if s.Kind() == reflect.Ptr {
|
||||||
|
s = s.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
numField := s.NumField()
|
||||||
|
meta := make([]map[string]interface{}, 0, numField+3)
|
||||||
|
|
||||||
|
for i := 0; i < numField; i++ {
|
||||||
|
f := s.Field(i)
|
||||||
|
jsonTag := strings.Split(f.Tag.Get("json"), ",")
|
||||||
|
|
||||||
|
if jsonTag[0] != "-" {
|
||||||
|
_type := f.Type
|
||||||
|
|
||||||
|
kind := f.Type.Kind()
|
||||||
|
if kind == reflect.Ptr {
|
||||||
|
_type = _type.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Anonymous {
|
||||||
|
// embed directly
|
||||||
|
embed := getStructMeta(_type, dontRecurse)
|
||||||
|
for _, e := range embed {
|
||||||
|
meta = append(meta, e)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fMeta := make(map[string]interface{})
|
||||||
|
|
||||||
|
fName := f.Name
|
||||||
|
if jsonTag[0] != "" {
|
||||||
|
fName = jsonTag[0]
|
||||||
|
}
|
||||||
|
fMeta["name"] = fName
|
||||||
|
|
||||||
|
fType := _type.Name()
|
||||||
|
if fType == "" {
|
||||||
|
fType = _type.String()
|
||||||
|
}
|
||||||
|
//fType = strings.Replace(fType, "*", "", -1)
|
||||||
|
fType = regexp.MustCompile("\\*[a-zA-Z0-9]*\\.?").ReplaceAllString(fType, "")
|
||||||
|
fMeta["type"] = fType
|
||||||
|
kind = _type.Kind()
|
||||||
|
fMeta["kind"] = kind.String()
|
||||||
|
|
||||||
|
if vT := f.Tag.Get("validator"); vT != "" {
|
||||||
|
fMeta["validator"] = strings.Split(vT, ",")
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
|
||||||
|
if vT := f.Tag.Get("class"); vT != "" {
|
||||||
|
fMeta["class"] = strings.Split(vT, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
if lT := f.Tag.Get("label"); lT != "" {
|
||||||
|
fMeta["label"] = lT
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if vM := f.Tag.Get("meta"); vM != "" {
|
||||||
|
m := strings.Split(vM, ";")
|
||||||
|
for _, mP := range m {
|
||||||
|
p := strings.Split(mP, "=")
|
||||||
|
if len(p) > 1 {
|
||||||
|
pp := strings.Split(p[1], ",")
|
||||||
|
if len(pp) == 1 {
|
||||||
|
fMeta[p[0]] = pp[0]
|
||||||
|
} else {
|
||||||
|
fMeta[p[0]] = pp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if kind == reflect.Struct && !dontRecurse[fType] {
|
||||||
|
dontRecurse[fType] = true
|
||||||
|
fMeta[kind.String()+"Of"] = getStructMeta(_type, dontRecurse)
|
||||||
|
} else if kind == reflect.Slice && !dontRecurse[fType] {
|
||||||
|
sliceEl := _type.Elem()
|
||||||
|
if sliceEl.Kind() == reflect.Ptr {
|
||||||
|
sliceEl = sliceEl.Elem()
|
||||||
|
}
|
||||||
|
if sliceEl.Kind() == reflect.Struct {
|
||||||
|
dontRecurse[fType] = true
|
||||||
|
fMeta[kind.String()+"Of"] = getStructMeta(sliceEl, dontRecurse)
|
||||||
|
} else {
|
||||||
|
fMeta[kind.String()+"Of"] = sliceEl.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
meta = append(meta, fMeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return meta
|
||||||
|
}
|
||||||
|
|
||||||
|
func getModelMeta(m mgocrud.ModelInterface) []map[string]interface{} {
|
||||||
|
dontRecurse := map[string]bool{
|
||||||
|
"Time": true,
|
||||||
|
"ObjectId": true,
|
||||||
|
}
|
||||||
|
modelType := reflect.ValueOf(m).Elem().Type()
|
||||||
|
|
||||||
|
return getStructMeta(modelType, dontRecurse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDocument(c *gin.Context, db *mgo.Database, m mgocrud.ModelInterface, selector bson.M) (mgocrud.ModelInterface, error) {
|
||||||
|
objectID, err := string2ObjectID(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newM := newModelOf(m).(mgocrud.ModelInterface)
|
||||||
|
newM.SetID(objectID)
|
||||||
|
err = mgocrud.ReadDocument(db, newM, selector)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newM, nil
|
||||||
|
}
|
||||||
114
login.go
Normal file
114
login.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package mgoapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
jwt "github.com/dgrijalva/jwt-go"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
mgo "gopkg.in/mgo.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoginModel is interface for modules which can be used for the login route
|
||||||
|
type LoginModel interface {
|
||||||
|
LoginCheck(db *mgo.Database) (tokenData interface{}, err error)
|
||||||
|
LoginResponse(token string) (interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) loginPostHandler(m LoginModel) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
lM := newModelOf(m).(LoginModel)
|
||||||
|
if err := c.Bind(lM); err != nil {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session := api.DBSession.Copy()
|
||||||
|
defer session.Close()
|
||||||
|
db := session.DB(api.DBName)
|
||||||
|
|
||||||
|
tokenData, err := lM.LoginCheck(db)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenData == nil {
|
||||||
|
c.JSON(403, gin.H{
|
||||||
|
"error": "login failed",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// build jwt
|
||||||
|
jwtToken := jwt.New(jwt.GetSigningMethod("HS256"))
|
||||||
|
oType := reflect.TypeOf(tokenData)
|
||||||
|
var objectType string
|
||||||
|
if oType.Kind() == reflect.Ptr {
|
||||||
|
objectType = oType.Elem().Name()
|
||||||
|
} else {
|
||||||
|
objectType = oType.Name()
|
||||||
|
}
|
||||||
|
if objectType == "" {
|
||||||
|
objectType = oType.String()
|
||||||
|
}
|
||||||
|
jwtToken.Claims = jwt.MapClaims{
|
||||||
|
"object": tokenData,
|
||||||
|
"type": objectType,
|
||||||
|
"exp": time.Now().Add(time.Minute * 60).Unix(),
|
||||||
|
}
|
||||||
|
token, err := jwtToken.SignedString(api.jwtSecret)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := lM.LoginResponse(token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateAuthToken checks if token is valid and returns its data
|
||||||
|
func (c *Context) ValidateAuthToken() (tokenObject map[string]interface{}, tokenType string, err error) {
|
||||||
|
token := c.Request.Header.Get(c.API.authenticationHeader)
|
||||||
|
if token == "" {
|
||||||
|
return nil, "", errors.New("empty token")
|
||||||
|
}
|
||||||
|
jwtToken, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
|
||||||
|
return []byte(c.API.jwtSecret), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if jwtToken == nil || jwtToken.Claims == nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
if err := jwtToken.Claims.Valid(); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
claims := jwtToken.Claims.(jwt.MapClaims)
|
||||||
|
|
||||||
|
_object, ok := claims["object"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, "", errors.New("token object data is invalid")
|
||||||
|
}
|
||||||
|
_type, ok := claims["type"].(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, "", errors.New("token object type is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
return _object, _type, err
|
||||||
|
}
|
||||||
105
register.go
Normal file
105
register.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package mgoapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.basehosts.de/gopackage/mgocrud"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
mgo "gopkg.in/mgo.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// API is wrapper for one RouterGroup and mgo DB
|
||||||
|
type API struct {
|
||||||
|
DBSession *mgo.Session
|
||||||
|
DBName string
|
||||||
|
routerGroup *gin.RouterGroup
|
||||||
|
jwtSecret []byte
|
||||||
|
authenticationHeader string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context is gin.Context with extras
|
||||||
|
type Context struct {
|
||||||
|
*gin.Context
|
||||||
|
API *API
|
||||||
|
DB *mgo.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns new instance of the API
|
||||||
|
func New(session *mgo.Session, dbname string, routerGroup *gin.RouterGroup) *API {
|
||||||
|
return &API{
|
||||||
|
DBSession: session,
|
||||||
|
DBName: dbname,
|
||||||
|
routerGroup: routerGroup,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var modelRegistry = make([]mgocrud.ModelInterface, 0, 0)
|
||||||
|
|
||||||
|
// RegisterModel setups gin routes for model GET, POST, PUT and DELETE
|
||||||
|
func (api *API) RegisterModel(m mgocrud.ModelInterface) {
|
||||||
|
|
||||||
|
modelRegistry = append(modelRegistry, m)
|
||||||
|
|
||||||
|
session := api.DBSession.Copy()
|
||||||
|
defer session.Close()
|
||||||
|
db := session.DB(api.DBName)
|
||||||
|
|
||||||
|
err := mgocrud.EnsureIndex(db, m)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cName := mgocrud.GetCollectionName(m)
|
||||||
|
colGroup := api.routerGroup.Group("/" + cName)
|
||||||
|
|
||||||
|
if i, ok := m.(interface {
|
||||||
|
Middleware(*Context)
|
||||||
|
}); ok {
|
||||||
|
colGroup.Use(func(c *gin.Context) {
|
||||||
|
i.Middleware(&Context{
|
||||||
|
Context: c,
|
||||||
|
API: api,
|
||||||
|
DB: db,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
colGroup.GET("/:id", api.collectionGetOneHandler(m))
|
||||||
|
colGroup.GET("", api.collectionGetHandler(m))
|
||||||
|
colGroup.POST("", api.collectionPostHandler(m))
|
||||||
|
colGroup.PUT("/:id", api.collectionPutHandler(m))
|
||||||
|
colGroup.DELETE("/:id", api.collectionDeleteHandler(m))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddMetaRoute adds a route for retrieving meta information for all models
|
||||||
|
// call after all registerModel calls
|
||||||
|
func (api *API) AddMetaRoute(route string) {
|
||||||
|
api.routerGroup.GET(route, func(c *gin.Context) {
|
||||||
|
modelMeta := make(map[string]interface{})
|
||||||
|
|
||||||
|
for _, m := range modelRegistry {
|
||||||
|
modelMeta[mgocrud.GetCollectionName(m)] = getModelMeta(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, map[string]interface{}{
|
||||||
|
"models": modelMeta,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, m := range modelRegistry {
|
||||||
|
api.routerGroup.GET("/_meta/"+mgocrud.GetCollectionName(m), api.collectionGetMetaHandler(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginConfig configures login method
|
||||||
|
type LoginConfig struct {
|
||||||
|
JWTSecret []byte
|
||||||
|
AuthenticationHeader string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLoginRoute adds a route for login
|
||||||
|
func (api *API) AddLoginRoute(route string, m LoginModel, c LoginConfig) {
|
||||||
|
api.jwtSecret = c.JWTSecret
|
||||||
|
api.authenticationHeader = c.AuthenticationHeader
|
||||||
|
api.routerGroup.POST(route, api.loginPostHandler(m))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user