From 1d36dd9bed0628ebacffd69661755a88a7736512 Mon Sep 17 00:00:00 2001 From: Sebastian Frank Date: Tue, 20 Mar 2018 14:15:01 +0100 Subject: [PATCH] Initial commit --- filter.go | 307 ++++++++++++++++++++++++++++++++++++++ handler.go | 417 ++++++++++++++++++++++++++++++++++++++++++++++++++++ helper.go | 164 +++++++++++++++++++++ login.go | 114 ++++++++++++++ register.go | 105 +++++++++++++ 5 files changed, 1107 insertions(+) create mode 100644 filter.go create mode 100644 handler.go create mode 100644 helper.go create mode 100644 login.go create mode 100644 register.go diff --git a/filter.go b/filter.go new file mode 100644 index 0000000..35095a9 --- /dev/null +++ b/filter.go @@ -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 +} diff --git a/handler.go b/handler.go new file mode 100644 index 0000000..9f34c84 --- /dev/null +++ b/handler.go @@ -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)) + } +} diff --git a/helper.go b/helper.go new file mode 100644 index 0000000..a5fa1dd --- /dev/null +++ b/helper.go @@ -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 +} diff --git a/login.go b/login.go new file mode 100644 index 0000000..89b17d1 --- /dev/null +++ b/login.go @@ -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 +} diff --git a/register.go b/register.go new file mode 100644 index 0000000..58784e3 --- /dev/null +++ b/register.go @@ -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)) +}