Skip to content

Instantly share code, notes, and snippets.

@brunoksato
Last active April 17, 2017 18:27
Show Gist options
  • Save brunoksato/0d6b95d4eb293f15fa5ffc416c8a5e0e to your computer and use it in GitHub Desktop.
Save brunoksato/0d6b95d4eb293f15fa5ffc416c8a5e0e to your computer and use it in GitHub Desktop.
Generics.go
//
//models
//
type Model struct {
ID uint `json:"id" gorm:"primary_key"`
CreatedAt time.Time `json:"-" sql:"DEFAULT:current_timestamp"`
UpdatedAt time.Time `json:"-" sql:"DEFAULT:current_timestamp"`
DeletedAt *time.Time `json:"-" settable:"false"`
}
type User struct {
Model
Name string `json:"name,omitempty" sql:"not null" valid:"length(3|50)"`
Password string `json:"password,omitempty"`
HashedPassword []byte `json:"-" sql:"hashed_password;not null"`
Birthday *time.Time `json:"birthday,omitempty"`
Image string `json:"img,omitempty"`
Gender uint `json:"gender"`
Phone string `json:"phone"`
Status uint `json:"status"`
}
type Category struct {
Model
Name string `json:"name,omitempty" sql:"not null"`
Description string `json:"description,omitempty"`
Image string `json:"image,omitempty"`
ImageMobileBackground string `json:"imb,omitempty"`
ImageBackground string `json:"ib,omitempty"`
Order uint `json:"order,omitempty"`
Status uint `json:"status"`
SubCategory []SubCategory `json:"sc,omitempty" fetch:"eager"` //this fetch use in BasicJoins automatic Preload
}
type SubCategory struct {
Model
Name string `json:"name,omitempty" sql:"not null"`
Description string `json:"description,omitempty"`
Image string `json:"image,omitempty"`
Status uint `json:"status"`
Category *Category `json:"c,omitempty"`
CategoryID uint `json:"cid" sql:"index"`
}
//
//handlers
//
func (ctx *Context) List(c echo.Context) error {
db := ctx.Database
db = BasicJoins(ctx, c, db)
db = BasicPaging(ctx, c, db, true)
items := reflect.New(reflect.SliceOf(ctx.Type)).Interface()
err := db.Find(items).Error
if err != nil {
return c.JSON(http.StatusBadRequest, NewServerError(err.Error()))
}
items = itemsOrEmptySlice(ctx.Type, items)
if ctx.Payload["ct"] != nil {
ctx.Payload["results"] = items
return c.JSON(http.StatusOK, ctx.Payload)
}
return c.JSON(http.StatusOK, items)
}
func (ctx *Context) Create(c echo.Context) error {
item := reflect.New(ctx.Type).Interface()
if err := c.Bind(item); err != nil {
return c.JSON(http.StatusBadRequest, NewServerError(err.Error()))
}
valerr := BasicValidationForCreate(ctx, c, item)
if valerr != nil {
return c.JSON(http.StatusBadRequest, NewServerError(valerr.Error()))
}
dberr := ctx.Database.Create(item).Error
if dberr != nil {
return c.JSON(http.StatusBadRequest, NewServerError(dberr.Error()))
}
return c.JSON(http.StatusCreated, item)
}
func (ctx *Context) Get(c echo.Context) error {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
return c.JSON(http.StatusNotFound, NewServerError(err.Error()))
}
item := reflect.New(ctx.Type).Interface()
db := ctx.Database
db = BasicJoins(ctx, c, db)
err = db.First(item, id).Error
if err != nil {
return c.JSON(http.StatusBadRequest, NewServerError(err.Error()))
}
return c.JSON(http.StatusOK, item)
}
func (ctx *Context) Update(c echo.Context) error {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
return c.JSON(http.StatusNotFound, NewServerError(err.Error()))
}
item := reflect.New(ctx.Type).Interface()
err = ctx.Database.First(item, id).Error
if err != nil {
return c.JSON(http.StatusBadRequest, NewServerError(err.Error()))
}
err = BasicValidationForUpdate(ctx, c, item)
if err != nil {
return c.JSON(http.StatusBadRequest, NewServerError(err.Error()))
}
if err := c.Bind(item); err != nil {
return c.JSON(http.StatusBadRequest, NewServerError(err.Error()))
}
ctx.Database.Set("gorm:save_associations", false).Save(item)
if ctx.Database.Error != nil {
return c.JSON(http.StatusBadRequest, NewServerError(ctx.Database.Error.Error()))
}
return c.JSON(http.StatusAccepted, item)
}
func (ctx *Context) Delete(c echo.Context) error {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
return c.JSON(http.StatusNotFound, NewServerError(err.Error()))
}
item := reflect.New(ctx.Type).Interface()
err = ctx.Database.First(item, id).Error
if err != nil {
return c.JSON(http.StatusBadRequest, NewServerError(err.Error()))
}
err = ctx.Database.Delete(item).Error
if err != nil {
return c.JSON(http.StatusBadRequest, NewServerError(err.Error()))
}
return c.JSON(http.StatusOK, item)
}
func BasicJoins(ctx *Context, c echo.Context, db *gorm.DB) *gorm.DB {
db = joinsFor(db, ctx)
return db
}
func joinsFor(db *gorm.DB, ctx *Context) *gorm.DB {
t := ctx.Type
elemT := t
if elemT.Kind() == reflect.Ptr {
elemT = elemT.Elem()
}
for i := 0; i < elemT.NumField(); i++ {
f := elemT.Field(i)
tag := elemT.Field(i).Tag
fetch := tag.Get("fetch")
if fetch == "eager" {
db = db.Preload(f.Name)
}
}
return db
}
func BasicPaging(ctx *Context, c echo.Context, db *gorm.DB, count bool) *gorm.DB {
st := c.QueryParam("start")
limit := c.QueryParam("limit")
if st != "" {
startIdx, _ := strconv.Atoi(st)
if startIdx > 0 {
db = db.Offset(startIdx)
}
}
if limit != "" {
limitIdx, _ := strconv.Atoi(limit)
if limitIdx > 0 {
db = db.Limit(limitIdx)
if count {
QueryTotalCount(ctx)
}
}
}
return db
}
func QueryTotalCount(ctx *Context) {
item := reflect.New(ctx.Type).Interface()
var n int
ctx.Database.Model(item).
Select("COUNT(*)").
Row().
Scan(&n)
ctx.Payload["ct"] = n
}
func itemsOrEmptySlice(t reflect.Type, items interface{}) interface{} {
if reflect.ValueOf(items).IsNil() {
items = reflect.MakeSlice(reflect.SliceOf(t), 0, 0)
}
return items
}
func BasicValidationForCreate(ctx *Context, c echo.Context, item interface{}) mondo.MondoError {
if model.IsValidator(ctx.Type) {
validator := item.(mondo.Validator)
return validator.ValidateForCreate()
} else {
return nil
}
}
func BasicValidationForUpdate(ctx *Context, c echo.Context, item interface{}) error {
if model.IsValidator(ctx.Type) {
return mondo.ValidateStructFields(item)
} else {
return nil
}
}
//
//middleware
//
type Context struct {
Database *gorm.DB
Payload map[string]interface{}
Type reflect.Type
ParentType reflect.Type
}
func PathParts(path string) []string {
parts := strings.Split(strings.Trim(path, " /"), "/")
return parts
}
func (ctx *Context) DetermineType(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
parts := PathParts(c.Path())
var pathType string
for i := 0; i < len(parts); i++ {
pathType = parts[i]
t, name := StringToType(pathType)
if t != nil {
if ctx.Type != nil {
ctx.ParentType = ctx.Type
}
ctx.Type = t
}
}
return next(c)
}
}
func StringToType(typeName string) (t reflect.Type, name string) {
switch typeName {
case "users":
var m mondo.User
t = reflect.TypeOf(m)
case "categories":
var m mondo.Category
t = reflect.TypeOf(m)
case "sub_categories":
var m mondo.SubCategory
t = reflect.TypeOf(m)
return
}
//
//routers
//
private.GET("/users", ctx.List)
private.GET("/users/:id", ctx.Get)
private.POST("/users", ctx.Create)
private.PUT("/users/:id", ctx.Update)
private.DELETE("/users/:id", ctx.Delete)
private.GET("/categories", ctx.List)
private.GET("/categories/:id", ctx.Get)
private.POST("/categories", ctx.Create)
private.PUT("/categories/:id", ctx.Update)
private.DELETE("/categories/:id", ctx.Delete)
private.GET("/sub_categories", ctx.List)
private.GET("/sub_categories/:id", ctx.Get)
private.POST("/sub_categories", ctx.Create)
private.PUT("/sub_categories/:id", ctx.Update)
private.DELETE("/sub_categories/:id", ctx.Delete)
//DB - All databases table need equal model name and router. Model.User = users(DB) and router too (users).
//create table categories()
//create table sub_categories()
//create table users()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment