diff --git a/README-zh_CN.md b/README-zh_CN.md index 61989d9..84211a5 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -17,7 +17,7 @@ Crud 是一个帮助编写增查改删(CRUD)服务的 golang 模块。有了 0. 安装: ```sh -go get -u github.com/cdfmlr/crud +go get -u github.com/tqrj/crud ``` 1. 然后你只需要定义模型,并在 orm 和 router 中注册它们: @@ -26,8 +26,8 @@ go get -u github.com/cdfmlr/crud package main import ( - "github.com/cdfmlr/crud/orm" - "github.com/cdfmlr/crud/router" + "github.com/tqrj/crud/orm" + "github.com/tqrj/crud/router" ) type Todo struct { @@ -155,11 +155,11 @@ DELETE /projects/:ProjectID/todos/:TodoID # delete an associated relationship **文档**: -- [go doc](https://pkg.go.dev/github.com/cdfmlr/crud) +- [go doc](https://pkg.go.dev/github.com/tqrj/crud) **实例**: -- [sshman](https://github.com/cdfmlr/sshman) 是一个更真实的例子,说明crud如何帮助你快速、轻松地建立一个真实世界的 CRUD REST API 项目。请务必看一下。 +- [sshman](https://github.com/tqrj/sshman) 是一个更真实的例子,说明crud如何帮助你快速、轻松地建立一个真实世界的 CRUD REST API 项目。请务必看一下。 ## 它是如何工作的 diff --git a/README.md b/README.md index 6b05a70..bd48306 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ English | [机翻中文](README-zh_CN.md) -[![Go Reference Badge](https://pkg.go.dev/badge/github.com/cdfmlr/crud.svg)](https://pkg.go.dev/github.com/cdfmlr/crud) [![Go Report Badge](https://goreportcard.com/badge/github.com/cdfmlr/crud)](https://goreportcard.com/report/github.com/cdfmlr/crud) +[![Go Reference Badge](https://pkg.go.dev/badge/github.com/tqrj/crud.svg)](https://pkg.go.dev/github.com/tqrj/crud) [![Go Report Badge](https://goreportcard.com/badge/github.com/tqrj/crud)](https://goreportcard.com/report/github.com/tqrj/crud) Crud is a golang package that helps writing CRUD servers. With this package, all you need is models, @@ -30,8 +30,8 @@ go get -u github.com/tqrj/crud package main import ( - "github.com/cdfmlr/crud/orm" - "github.com/cdfmlr/crud/router" + "github.com/tqrj/crud/orm" + "github.com/tqrj/crud/router" ) type Todo struct { @@ -180,11 +180,11 @@ to build your own CRUD API services: **Documents**: -- [go doc](https://pkg.go.dev/github.com/cdfmlr/crud) +- [go doc](https://pkg.go.dev/github.com/tqrj/crud) **Examples**: -- [sshman](https://github.com/cdfmlr/sshman) is a more real world example of how +- [sshman](https://github.com/tqrj/sshman) is a more real world example of how crud can help you build a CRUD REST API project fast and easily. Please check it out. diff --git a/config/config.go b/config/config.go index 5b1c5ed..e4ed70c 100644 --- a/config/config.go +++ b/config/config.go @@ -5,9 +5,9 @@ import ( "encoding/json" "errors" "fmt" - "github.com/cdfmlr/crud/log" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" + "github.com/tqrj/crud/log" "reflect" "strings" ) @@ -15,9 +15,9 @@ import ( var logger = log.ZoneLogger("crud/config") // Init reads configure, write values into given configModel -// - FromFile: read config from file -// - FromEnv: read config from environment variables -// - WatchFileChange: watch config file and reload config when changed +// - FromFile: read config from file +// - FromEnv: read config from environment variables +// - WatchFileChange: watch config file and reload config when changed func Init(configModel any, options ...Option) error { for _, option := range options { err := option(configModel) @@ -46,8 +46,9 @@ func FromFile(path string) Option { } // WatchFileChange works with FromFile: -// var config MyConfig -// config.Init(&config, FromFile(path), WatchFileChange(hook)) +// +// var config MyConfig +// config.Init(&config, FromFile(path), WatchFileChange(hook)) // // WatchFileChange watch current viper config file, // and reload config when changed. @@ -69,13 +70,15 @@ func WatchFileChange(hook func(oldConfig any, newConfig any)) Option { // FromEnv reads config from environment variables, and unmarshal to config // prefix is the prefix of environment variables: -// type MyConfig struct { -// Foo struct { -// Bar string -// } -// } -// config := MyConfig{} -// config.Init(&config, FromEnv("MYAPP")) +// +// type MyConfig struct { +// Foo struct { +// Bar string +// } +// } +// config := MyConfig{} +// config.Init(&config, FromEnv("MYAPP")) +// // will read `config.Foo.Bar` from env `MYAPP_FOO_BAR`. func FromEnv(prefix string) Option { return func(config any) error { diff --git a/controller/controller.go b/controller/controller.go index aaf35b3..99bbbdd 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -5,13 +5,19 @@ // is actually a gin.HandlerFunc: // // - GET /models => GetListHandler[Model]: to retrieve a model list +// // - GET /models/:id => GetByIDHandler[Model]: to retrieve a model by id +// // - POST /models => CreateHandler[Model] : to create a new model +// // - PUT /models/:id => UpdateHandler[Model] : to update an existing model +// // - DELETE /models/:id => DeleteHandler[Model] : to delete an existing model // // - GET /models/:id/field => GetFieldHandler[Model] : to retrieve a field (nested model) of a model +// // - POST /models/:id/field => CreateNestedHandler[Model] : to create a nested model (association) +// // - DELETE /models/:id/field => DeleteNestedHandler[Model] : to delete an association record // // The controller are all generic functions, which is available in Go 1.18 and @@ -21,12 +27,13 @@ // go have no way to infer them. // // Notice that there is not a UpdateNestedHandler, because: -// PUT /models/:id/field/:id == PUT /field/:id +// +// PUT /models/:id/field/:id == PUT /field/:id // // [Gin]: https://github.com/gin-gonic/gin // [Go generics tutorial]: https://go.dev/doc/tutorial/generics package controller -import "github.com/cdfmlr/crud/log" +import "github.com/tqrj/crud/log" var logger = log.ZoneLogger("crud/controller") diff --git a/controller/create.go b/controller/create.go index ba97e68..a6fcffc 100644 --- a/controller/create.go +++ b/controller/create.go @@ -1,23 +1,25 @@ package controller import ( - "github.com/cdfmlr/crud/orm" - "github.com/cdfmlr/crud/service" "github.com/gin-gonic/gin" + "github.com/tqrj/crud/orm" + "github.com/tqrj/crud/service" "reflect" ) // CreateHandler handles -// POST /T +// +// POST /T +// // creates a new model T, responds with the created model T if successful. // // Request body: -// - {...} // fields of the model T +// - {...} // fields of the model T // // Response: -// - 200 OK: { T: {...} } -// - 400 Bad Request: { error: "request band failed" } -// - 422 Unprocessable Entity: { error: "create process failed" } +// - 200 OK: { T: {...} } +// - 400 Bad Request: { error: "request band failed" } +// - 422 Unprocessable Entity: { error: "create process failed" } func CreateHandler[T any]() gin.HandlerFunc { return func(c *gin.Context) { var model T @@ -40,20 +42,23 @@ func CreateHandler[T any]() gin.HandlerFunc { } // CreateNestedHandler handles -// POST /P/:parentIDRouteParam/T +// +// POST /P/:parentIDRouteParam/T +// // where: -// - P is the parent model, T is the child model -// - parentIDRouteParam is the route param name of the parent model P -// - field is the field name of the child model T in the parent model P +// - P is the parent model, T is the child model +// - parentIDRouteParam is the route param name of the parent model P +// - field is the field name of the child model T in the parent model P +// // responds with the updated parent model P // // Request body: -// - {...} // fields of the child model T +// - {...} // fields of the child model T // // Response: -// - 200 OK: { P: {...} } -// - 400 Bad Request: { error: "request band failed" } -// - 422 Unprocessable Entity: { error: "create process failed" } +// - 200 OK: { P: {...} } +// - 400 Bad Request: { error: "request band failed" } +// - 422 Unprocessable Entity: { error: "create process failed" } func CreateNestedHandler[P orm.Model, T orm.Model](parentIDRouteParam string, field string) gin.HandlerFunc { return func(c *gin.Context) { parentID := c.Param(parentIDRouteParam) diff --git a/controller/delete.go b/controller/delete.go index d92929b..06e7c95 100644 --- a/controller/delete.go +++ b/controller/delete.go @@ -1,21 +1,23 @@ package controller import ( - "github.com/cdfmlr/crud/orm" - "github.com/cdfmlr/crud/service" "github.com/gin-gonic/gin" + "github.com/tqrj/crud/orm" + "github.com/tqrj/crud/service" ) // DeleteHandler handles -// DELETE /T/:idParam +// +// DELETE /T/:idParam +// // Deletes the model T with the given id. // // Request body: none // // Response: -// - 200 OK: { deleted: true } -// - 400 Bad Request: { error: "missing id" } -// - 422 Unprocessable Entity: { error: "delete process failed" } +// - 200 OK: { deleted: true } +// - 400 Bad Request: { error: "missing id" } +// - 422 Unprocessable Entity: { error: "delete process failed" } func DeleteHandler[T orm.Model](idParam string) gin.HandlerFunc { return func(c *gin.Context) { id := c.Param(idParam) @@ -39,19 +41,21 @@ func DeleteHandler[T orm.Model](idParam string) gin.HandlerFunc { } // DeleteNestedHandler handles -// DELETE /P/:parentIdParam/T/:childIdParam +// +// DELETE /P/:parentIdParam/T/:childIdParam +// // where: -// - P is the parent model, T is the child model -// - parentIdParam is the route param name of the parent model P -// - childIdParam is the route param name of the child model T in the parent model P -// - field is the field name of the child model T in the parent model P +// - P is the parent model, T is the child model +// - parentIdParam is the route param name of the parent model P +// - childIdParam is the route param name of the child model T in the parent model P +// - field is the field name of the child model T in the parent model P // // Request body: none // // Response: -// - 200 OK: { deleted: true } -// - 400 Bad Request: { error: "missing id" } -// - 422 Unprocessable Entity: { error: "delete process failed" } +// - 200 OK: { deleted: true } +// - 400 Bad Request: { error: "missing id" } +// - 422 Unprocessable Entity: { error: "delete process failed" } func DeleteNestedHandler[P orm.Model, T orm.Model](parentIdParam string, field string, childIdParam string) gin.HandlerFunc { return func(c *gin.Context) { parentId := c.Param(parentIdParam) diff --git a/controller/get.go b/controller/get.go index 735473a..d5825ad 100644 --- a/controller/get.go +++ b/controller/get.go @@ -2,19 +2,19 @@ package controller import ( "context" - "github.com/cdfmlr/crud/orm" - "github.com/cdfmlr/crud/service" "github.com/gin-gonic/gin" + "github.com/tqrj/crud/orm" + "github.com/tqrj/crud/service" "reflect" ) // GetRequestOptions is the query options (?opt=val) for GET requests: // -// limit=10&offset=4& # pagination -// order_by=id&desc=true& # ordering -// filter_by=name&filter_value=John& # filtering -// total=true& # return total count (all available records under the filter, ignoring pagination) -// preload=Product&preload=Product.Manufacturer # preloading: loads nested models as well +// limit=10&offset=4& # pagination +// order_by=id&desc=true& # ordering +// filter_by=name&filter_value=John& # filtering +// total=true& # return total count (all available records under the filter, ignoring pagination) +// preload=Product&preload=Product.Manufacturer # preloading: loads nested models as well // // It is used in GetListHandler, GetByIDHandler and GetFieldHandler, to bind // the query parameters in the GET request url. @@ -30,16 +30,19 @@ type GetRequestOptions struct { } // GetListHandler handles -// GET /T +// +// GET /T +// // It returns a list of models. // // QueryOptions (See GetRequestOptions for more details): -// limit, offset, order_by, desc, filter_by, filter_value, preload, total. +// +// limit, offset, order_by, desc, filter_by, filter_value, preload, total. // // Response: -// - 200 OK: { Ts: [{...}, ...] } -// - 400 Bad Request: { error: "request band failed" } -// - 422 Unprocessable Entity: { error: "get process failed" } +// - 200 OK: { Ts: [{...}, ...] } +// - 400 Bad Request: { error: "request band failed" } +// - 422 Unprocessable Entity: { error: "get process failed" } func GetListHandler[T any]() gin.HandlerFunc { return func(c *gin.Context) { var request GetRequestOptions @@ -77,14 +80,15 @@ func GetListHandler[T any]() gin.HandlerFunc { } // GetByIDHandler handles -// GET /T/:idParam +// +// GET /T/:idParam // // QueryOptions (See GetRequestOptions for more details): preload // // Response: -// - 200 OK: { T: {...} } -// - 400 Bad Request: { error: "request band failed" } -// - 422 Unprocessable Entity: { error: "get process failed" } +// - 200 OK: { T: {...} } +// - 400 Bad Request: { error: "request band failed" } +// - 422 Unprocessable Entity: { error: "get process failed" } func GetByIDHandler[T orm.Model](idParam string) gin.HandlerFunc { return func(c *gin.Context) { var request GetRequestOptions @@ -109,18 +113,23 @@ func GetByIDHandler[T orm.Model](idParam string) gin.HandlerFunc { } // GetFieldHandler handles -// GET /T/:idParam/field +// +// GET /T/:idParam/field // // QueryOptions (See GetRequestOptions for more details): -// limit, offset, order_by, desc, filter_by, filter_value, preload, total. +// +// limit, offset, order_by, desc, filter_by, filter_value, preload, total. +// // Notice, all GetRequestOptions will be conditions for the field, for example: -// GET /user/123/order?preload=Product +// +// GET /user/123/order?preload=Product +// // Preloads User.Order.Product instead of User.Product. // // Response: -// - 200 OK: { Fs: [{...}, ...] } // field models -// - 400 Bad Request: { error: "request band failed" } -// - 422 Unprocessable Entity: { error: "get process failed" } +// - 200 OK: { Fs: [{...}, ...] } // field models +// - 400 Bad Request: { error: "request band failed" } +// - 422 Unprocessable Entity: { error: "get process failed" } func GetFieldHandler[T orm.Model](idParam string, field string) gin.HandlerFunc { field = nameToField(field, *new(T)) diff --git a/controller/update.go b/controller/update.go index c7de9ee..843717a 100644 --- a/controller/update.go +++ b/controller/update.go @@ -1,24 +1,26 @@ package controller import ( - "github.com/cdfmlr/crud/log" - "github.com/cdfmlr/crud/orm" - "github.com/cdfmlr/crud/service" "github.com/gin-gonic/gin" + "github.com/tqrj/crud/log" + "github.com/tqrj/crud/orm" + "github.com/tqrj/crud/service" ) // UpdateHandler handles -// PUT /T/:idParam +// +// PUT /T/:idParam +// // Updates the model T with the given id. // // Request body: -// - {"field": "new_value", ...} // fields to update +// - {"field": "new_value", ...} // fields to update // // Response: -// - 200 OK: { updated: true } -// - 400 Bad Request: { error: "missing id or bind fields failed" } -// - 404 Not Found: { error: "record with id not found" } -// - 422 Unprocessable Entity: { error: "update process failed" } +// - 200 OK: { updated: true } +// - 400 Bad Request: { error: "missing id or bind fields failed" } +// - 404 Not Found: { error: "record with id not found" } +// - 422 Unprocessable Entity: { error: "update process failed" } func UpdateHandler[T orm.Model](idParam string) gin.HandlerFunc { return func(c *gin.Context) { var model T diff --git a/example/todolist.go b/example/todolist.go index 1708448..c0f2f03 100644 --- a/example/todolist.go +++ b/example/todolist.go @@ -1,8 +1,8 @@ package main import ( - "github.com/cdfmlr/crud/orm" - "github.com/cdfmlr/crud/router" + "github.com/tqrj/crud/orm" + "github.com/tqrj/crud/router" ) type Todo struct { diff --git a/log/log.go b/log/log.go index f2f71a6..e9e0321 100644 --- a/log/log.go +++ b/log/log.go @@ -2,10 +2,10 @@ package log import ( "fmt" - "github.com/cdfmlr/crud/pkg/ginlogrus" - "github.com/cdfmlr/crud/pkg/gormlogrus" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" + "github.com/tqrj/crud/pkg/ginlogrus" + "github.com/tqrj/crud/pkg/gormlogrus" ) // Level is the level of log: LevelDebug, LevelInfo, LevelWarn, LevelError @@ -80,7 +80,7 @@ func WithHook(hook logrus.Hook) LoggerOption { } // DefaultLoggerOptions = WithLevel(LevelDebug) + WithReportCaller(false) -// + WithHook(RequestIDHook()) +// - WithHook(RequestIDHook()) func DefaultLoggerOptions() []LoggerOption { return []LoggerOption{ WithLevel(LevelDebug), diff --git a/orm/orm.go b/orm/orm.go index e0a5441..fa67897 100644 --- a/orm/orm.go +++ b/orm/orm.go @@ -1,7 +1,7 @@ package orm import ( - "github.com/cdfmlr/crud/log" + "github.com/tqrj/crud/log" "gorm.io/gorm" "gorm.io/driver/mysql" diff --git a/router/crud.go b/router/crud.go index b7300b7..2585fa1 100644 --- a/router/crud.go +++ b/router/crud.go @@ -2,32 +2,40 @@ package router import ( "fmt" - "github.com/cdfmlr/crud/controller" - "github.com/cdfmlr/crud/orm" "github.com/gin-gonic/gin" + "github.com/tqrj/crud/controller" + "github.com/tqrj/crud/orm" "reflect" ) // Crud add a group of CRUD routes for model T to the base router // on relativePath. For example, if the base router handles route to -// /base +// +// /base +// // then the CRUD routes will be: -// GET|POST|PUT|DELETE /base/relativePath +// +// GET|POST|PUT|DELETE /base/relativePath +// // where relativePath is recommended to be the plural form of the model name. // // Example: -// r := gin.Default() -// Crud[User](r, "/users") +// +// r := gin.Default() +// Crud[User](r, "/users") +// // adds the following routes: -// GET /users/ -// GET /users/:UserId -// POST /users/ -// PUT /users/:UserId -// DELETE /users/:UserId +// +// GET /users/ +// GET /users/:UserId +// POST /users/ +// PUT /users/:UserId +// DELETE /users/:UserId +// // and with options parameters, it's optional to add the following routes: -// - GetNested() => GET /users/:UserId/friends -// - CreateNested() => POST /users/:UserId/friends -// - DeleteNested() => DELETE /users/:UserId/friends/:FriendId +// - GetNested() => GET /users/:UserId/friends +// - CreateNested() => POST /users/:UserId/friends +// - DeleteNested() => DELETE /users/:UserId/friends/:FriendId func Crud[T orm.Model](base gin.IRouter, relativePath string, options ...CrudOption) gin.IRouter { group := base.Group(relativePath) @@ -56,11 +64,12 @@ func Crud[T orm.Model](base gin.IRouter, relativePath string, options ...CrudOpt type CrudOption func(group *gin.RouterGroup) *gin.RouterGroup // crud add CRUD routes for model T to the group: -// GET / -// GET /:idParam -// POST / -// PUT /:idParam -// DELETE /:idParam +// +// GET / +// GET /:idParam +// POST / +// PUT /:idParam +// DELETE /:idParam func crud[T orm.Model]() CrudOption { idParam := getIdParam[T]() return func(group *gin.RouterGroup) *gin.RouterGroup { @@ -76,7 +85,8 @@ func crud[T orm.Model]() CrudOption { } // GetNested add a GET route to the group for querying a nested model: -// GET /:parentIdParam/field +// +// GET /:parentIdParam/field func GetNested[P orm.Model, N orm.Model](field string) CrudOption { parentIdParam := getIdParam[P]() return func(group *gin.RouterGroup) *gin.RouterGroup { @@ -101,7 +111,8 @@ func GetNested[P orm.Model, N orm.Model](field string) CrudOption { } // CreateNested add a POST route to the group for creating a nested model: -// POST /:parentIdParam/field +// +// POST /:parentIdParam/field func CreateNested[P orm.Model, N orm.Model](field string) CrudOption { parentIdParam := getIdParam[P]() return func(group *gin.RouterGroup) *gin.RouterGroup { @@ -122,7 +133,8 @@ func CreateNested[P orm.Model, N orm.Model](field string) CrudOption { } // DeleteNested add a DELETE route to the group for deleting a nested model: -// DELETE /:parentIdParam/field/:childIdParam +// +// DELETE /:parentIdParam/field/:childIdParam func DeleteNested[P orm.Model, T orm.Model](field string) CrudOption { parentIdParam := getIdParam[P]() childIdParam := getIdParam[T]() diff --git a/router/router.go b/router/router.go index b562d44..2ab2744 100644 --- a/router/router.go +++ b/router/router.go @@ -1,10 +1,10 @@ package router import ( - "github.com/cdfmlr/crud/log" - gin_request_id "github.com/cdfmlr/crud/pkg/gin-request-id" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" + "github.com/tqrj/crud/log" + gin_request_id "github.com/tqrj/crud/pkg/gin-request-id" ) var logger = log.ZoneLogger("crud/router") diff --git a/service/create.go b/service/create.go index 906b1de..d793115 100644 --- a/service/create.go +++ b/service/create.go @@ -2,7 +2,7 @@ package service import ( "context" - "github.com/cdfmlr/crud/orm" + "github.com/tqrj/crud/orm" "gorm.io/gorm" ) @@ -10,47 +10,53 @@ import ( // Nested models associated with the model will be created as well. // // There are two mode of creating a model: -// - IfNotExist: creates a model record if it does not exist. -// - NestInto: creates a nested model of the parent model. +// - IfNotExist: creates a model record if it does not exist. +// - NestInto: creates a nested model of the parent model. // // Note: // -// user := User{ profile: Profile{ ... } } -// Create(&user, IfNotExist()) // creates user, user.profile +// user := User{ profile: Profile{ ... } } +// Create(&user, IfNotExist()) // creates user, user.profile // -// group := GetByID[Group](123) -// Create(&user, NestInto(&group, "users")) -// // user is already in the database: just add it into group.users +// group := GetByID[Group](123) +// Create(&user, NestInto(&group, "users")) +// // user is already in the database: just add it into group.users func Create(ctx context.Context, model any, in CreateMode) error { return in(ctx, model) } // CreateMode is the way to create a model: -// - IfNotExist: creates a model if it does not exist. -// - NestInto: creates a nested model of the parent model. +// - IfNotExist: creates a model if it does not exist. +// - NestInto: creates a nested model of the parent model. // // TODO: I dont think it's reasonable to (ab)use functional option pattern here -// to handle different kinds of creates (CreateMode). It is a temporary -// solution and should be replaced by seperated functions, say, -// Create(model) and CreateNested(parentID, field, child). +// +// to handle different kinds of creates (CreateMode). It is a temporary +// solution and should be replaced by seperated functions, say, +// Create(model) and CreateNested(parentID, field, child). type CreateMode func(ctx context.Context, modelToCreate any) error // NestInto creates a nested model of the parent model in the database. // Say, if you have a model User and a model Profile: -// CREATE TABLE `user` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, ...) -// CREATE TABLE `profile` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, ...) -// CREATE TABLE `user_profiles` ( // a join table -// `user_id` INTEGER NOT NULL, -// `profile_id` INTEGER NOT NULL, -// PRIMARY KEY (`user_id`, `profile_id`) -// FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) -// FOREIGN KEY (`profile_id`) REFERENCES `profile` (`id`) -// ) +// +// CREATE TABLE `user` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, ...) +// CREATE TABLE `profile` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, ...) +// CREATE TABLE `user_profiles` ( // a join table +// `user_id` INTEGER NOT NULL, +// `profile_id` INTEGER NOT NULL, +// PRIMARY KEY (`user_id`, `profile_id`) +// FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) +// FOREIGN KEY (`profile_id`) REFERENCES `profile` (`id`) +// ) +// // You can create a Profile of a User (i.e. the user has the profile) by calling -// Create(&userProfile, NestInto(&user)) +// +// Create(&userProfile, NestInto(&user)) +// // The userProfile will be created in the database and associated with the user: -// INSERT INTO profile ... -// INSERT INTO user_profiles (user_id, profile_id) +// +// INSERT INTO profile ... +// INSERT INTO user_profiles (user_id, profile_id) // // This is useful to handle POSTs like /api/users/{user_id}/profile func NestInto(parent any, field string) CreateMode { diff --git a/service/delete.go b/service/delete.go index 819b4f5..54fc566 100644 --- a/service/delete.go +++ b/service/delete.go @@ -2,7 +2,7 @@ package service import ( "context" - "github.com/cdfmlr/crud/orm" + "github.com/tqrj/crud/orm" ) // Delete a model from database. diff --git a/service/get.go b/service/get.go index 54c930e..d7fb5ce 100644 --- a/service/get.go +++ b/service/get.go @@ -4,7 +4,7 @@ import ( "context" "errors" "fmt" - "github.com/cdfmlr/crud/orm" + "github.com/tqrj/crud/orm" "gorm.io/gorm" "gorm.io/gorm/clause" ) @@ -12,7 +12,9 @@ import ( // Get fetch a single model T into dest. // // Shout out to GORM for the feature called Smart Select Fields: -// https://gorm.io/docs/advanced_query.html#Smart-Select-Fields , +// +// https://gorm.io/docs/advanced_query.html#Smart-Select-Fields , +// // we can Get a specific part of fields of model T into a "view model" struct. // So the generic type T is the type of the Model (which mapping to a scheme, // i.e. a table in the database), while the parameter dest given the type of the @@ -22,10 +24,14 @@ import ( // // Use FilterBy and Where options to query on specific fields, and by adding // Preload options, you can preload relationships, for example: -// Get[User](&user, FilterBy("id", 10), Preload("Sessions"))) +// +// Get[User](&user, FilterBy("id", 10), Preload("Sessions"))) +// // means: -// SELECT * FROM users WHERE id = 10; // into dest -// SELECT * FROM sessions WHERE user_id = 10; // into user.Sessions +// +// SELECT * FROM users WHERE id = 10; // into dest +// SELECT * FROM sessions WHERE user_id = 10; // into user.Sessions +// // Because this getting model by id is a common operation, a shortcut GetByID // is provided. (but you still have to add Preload options if needed) func Get[T any](ctx context.Context, dest any, options ...QueryOption) error { @@ -83,20 +89,23 @@ func GetByID[T orm.Model](ctx context.Context, id any, dest any, options ...Quer // - WithPage(limit, offset) => pagination // - OrderBy(field, descending) => ordering // - FilterBy(field, value) => WHERE field=value condition -// - Where(query, args...) => for more complicated queries +// - Where(query, args...) => for more complicated queries // - Preload(field) => preload a relationship -// - PreloadAll() => preload all associations +// - PreloadAll() => preload all associations // // Example: -// GetMany[User](&users, -// WithPage(10, 0), -// OrderBy("age", true), -// FilterBy("name", "John")) +// +// GetMany[User](&users, +// WithPage(10, 0), +// OrderBy("age", true), +// FilterBy("name", "John")) +// // means: -// SELECT * FROM users -// WHERE name = "John" -// ORDER BY age desc -// LIMIT 10 OFFSET 0; // into users +// +// SELECT * FROM users +// WHERE name = "John" +// ORDER BY age desc +// LIMIT 10 OFFSET 0; // into users func GetMany[T any](ctx context.Context, dest any, options ...QueryOption) error { logger := logger.WithContext(ctx). WithField("model", fmt.Sprintf("%T", *new(T))). @@ -221,9 +230,12 @@ func OrderBy(field string, descending bool) QueryOption { // It can be applied multiple times (for multiple conditions). // // Example: -// GetMany[User](&users, FilterBy("name", "John"), FilterBy("age", 10)) +// +// GetMany[User](&users, FilterBy("name", "John"), FilterBy("age", 10)) +// // means: -// SELECT * FROM users WHERE name = "John" AND age = 10 ; // into users +// +// SELECT * FROM users WHERE name = "John" AND age = 10 ; // into users func FilterBy(field string, value any) QueryOption { return func(tx *gorm.DB) *gorm.DB { return tx.Where(map[string]any{field: value}) @@ -232,12 +244,16 @@ func FilterBy(field string, value any) QueryOption { // Where offers a more flexible way to set WHERE conditions. // Equivalent to gorm.DB.Where(...), see: -// https://gorm.io/docs/query.html#Conditions +// +// https://gorm.io/docs/query.html#Conditions // // Example: -// GetMany[User](&users, Where("name = ? AND age > ?", "John", 10)) +// +// GetMany[User](&users, Where("name = ? AND age > ?", "John", 10)) +// // means: -// SELECT * FROM users WHERE name = "John" AND age > 10 ; // into users +// +// SELECT * FROM users WHERE name = "John" AND age > 10 ; // into users func Where(query any, args ...any) QueryOption { return func(tx *gorm.DB) *gorm.DB { return tx.Where(query, args...) diff --git a/service/service.go b/service/service.go index 57ce9d6..1b835fa 100644 --- a/service/service.go +++ b/service/service.go @@ -4,7 +4,7 @@ // your own services with the orm.DB (a *gorm.DB) instance. package service -import "github.com/cdfmlr/crud/log" +import "github.com/tqrj/crud/log" // TODO: use orm.Model instead of any diff --git a/service/update.go b/service/update.go index 68664d4..cb77918 100644 --- a/service/update.go +++ b/service/update.go @@ -4,7 +4,7 @@ import ( "context" "errors" "fmt" - "github.com/cdfmlr/crud/orm" + "github.com/tqrj/crud/orm" ) // Update all fields of an existing model in database.