├── .gitignore ├── LICENSE ├── README.md ├── README_zh.md ├── app ├── api │ └── v1 │ │ ├── auth.go │ │ ├── migrate.go │ │ └── v1.go ├── middleware │ ├── auth.go │ ├── cors.go │ └── static.go ├── resource │ └── user.go └── web │ ├── apidoc.go │ └── home.go ├── config.example.yaml ├── config ├── app.go ├── cache.go ├── database.go ├── jwt.go ├── log.go └── redis.go ├── core ├── app │ └── app.go ├── auth │ └── auth.go ├── cache │ ├── cache.go │ └── rediscache.go ├── config │ └── config.go ├── db │ └── db.go ├── redis │ └── redis.go ├── request │ └── request.go ├── resource │ └── resource.go ├── response │ └── response.go └── service │ ├── cache.go │ ├── database.go │ ├── gin.go │ ├── logger.go │ ├── redis.go │ ├── router.go │ ├── service.go │ ├── translator.go │ └── validator.go ├── ent ├── client.go ├── config.go ├── context.go ├── ent.go ├── enttest │ └── enttest.go ├── generate.go ├── hook │ └── hook.go ├── migrate │ ├── migrate.go │ └── schema.go ├── mutation.go ├── paginator.go ├── predicate │ └── predicate.go ├── runtime.go ├── runtime │ └── runtime.go ├── schema │ ├── schema.go │ └── user.go ├── template │ └── paginator.tmpl ├── tx.go ├── user.go ├── user │ ├── user.go │ └── where.go ├── user_create.go ├── user_delete.go ├── user_query.go └── user_update.go ├── global └── global.go ├── go.mod ├── go.sum ├── locales ├── en.yml └── zh.yml ├── main.go ├── routes ├── api.go └── web.go ├── static ├── apidoc.yaml ├── css │ └── swagger-ui.css ├── favicon.ico ├── images │ ├── swagger-favicon-16x16.png │ └── swagger-favicon-32x32.png └── js │ ├── swagger-ui-bundle.js │ └── swagger-ui-standalone-preset.js ├── templates ├── 404.tmpl ├── 500.tmpl ├── apidoc.tmpl └── home.tmpl └── util ├── file └── file.go ├── hash └── hash.go └── str └── str.go /.gitignore: -------------------------------------------------------------------------------- 1 | tmp/ 2 | logs/ 3 | config.yaml 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 fitv 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## MIN - an API skeleton based on Gin 2 | 3 | [简体中文](README_zh.md) 4 | 5 | ## Packages used 6 | - [Gin](https://github.com/gin-gonic/gin) 7 | - [ent](https://entgo.io/ent) 8 | - [viper](https://github.com/spf13/viper) 9 | - [jwt-go](https://github.com/golang-jwt/jwt) 10 | - [go-redis](https://github.com/go-redis/redis) 11 | - [validator](https://github.com/go-playground/validator) 12 | - [swagger-ui](https://github.com/swagger-api/swagger-ui) 13 | 14 | ## Features 15 | - Easily build your `RESTful APIs` 16 | - `JWT` Authorization 17 | - Configuration 18 | - Cache based on `Redis` 19 | - `ent` Paginator 20 | - API Resource Transformer 21 | - Multi-language translation support 22 | - Swagger UI (render YAML file) 23 | 24 | ## Installation 25 | Clone repository 26 | ``` 27 | git clone https://github.com/fitv/min.git 28 | ``` 29 | 30 | Copy the configuration file 31 | ``` 32 | cd min && cp config.example.yaml config.yaml 33 | ``` 34 | 35 | Run Application 36 | ``` 37 | go run main.go 38 | ``` 39 | 40 | Build Application 41 | ``` 42 | go build -o min main.go 43 | ``` 44 | 45 | Open the API document URL in your browser 46 | ``` 47 | http://127.0.0.1:3000/apidoc 48 | ``` 49 | 50 | Run database migration 51 | ``` 52 | http://127.0.0.1:3000/api/v1/migrate 53 | ``` 54 | 55 | ## Usage 56 | 57 | ### Cache 58 | ```go 59 | import "github.com/fitv/min/global" 60 | 61 | global.Cache().Set(ctx, "key", "value", time.Minute) 62 | global.Cache().Get(ctx, "key") // value 63 | global.Cache().Has(ctx, "key") // true 64 | global.Cache().TTL(ctx, "key") // time.Minute 65 | global.Cache().Del(ctx, "key") // true 66 | ``` 67 | 68 | ### Logger 69 | ```go 70 | import "github.com/fitv/min/global" 71 | 72 | global.Log().Debug("debug") 73 | global.Log().Info("info") 74 | global.Log().Warn("warn") 75 | global.Log().Error("error") 76 | global.Log().Panic("panic") 77 | global.Log().Fatal("fatal") 78 | ``` 79 | 80 | ### Multi-language translation 81 | ```go 82 | import "github.com/fitv/min/global" 83 | 84 | global.Lang().Trans("hello.world") // world 85 | global.Lang().Locale("zh").Trans("hello.world") // 世界 86 | ``` 87 | 88 | ### Paginator & API Resource Transformer 89 | ```go 90 | package user 91 | 92 | import ( 93 | "context" 94 | "strconv" 95 | 96 | "github.com/fitv/min/app/resource" 97 | "github.com/fitv/min/core/response" 98 | "github.com/fitv/min/global" 99 | "github.com/gin-gonic/gin" 100 | ) 101 | 102 | type User struct{} 103 | 104 | // Index returns a list of users with pagination 105 | func (User) Index(c *gin.Context) { 106 | paginator, err := global.Ent().User. 107 | Query(). 108 | Paginate(ctx, c) 109 | if err != nil { 110 | response.HandleEntError(c, err) 111 | return 112 | } 113 | resource.NewUserPaginator(c, paginator).Response() 114 | } 115 | 116 | // Info returns user information 117 | func (User) Info(c *gin.Context) { 118 | id, _ := strconv.Atoi(c.Param("id")) 119 | user, err := global.Ent().User.Get(ctx, id) 120 | if err != nil { 121 | response.HandleEntError(c, err) 122 | return 123 | } 124 | resource.NewUser(c, user).Append(gin.H{ 125 | "role": "staff", 126 | }).Wrap("user").Response() 127 | } 128 | ``` 129 | 130 | User Index response 131 | ```json 132 | { 133 | "current_page": 1, 134 | "per_page": 15, 135 | "last_page": 1, 136 | "total": 2, 137 | "data": [ 138 | { 139 | "id": 1, 140 | "username": "u1" 141 | }, 142 | { 143 | "id": 2, 144 | "username": "u2" 145 | } 146 | ] 147 | } 148 | ``` 149 | 150 | User Info response 151 | ```json 152 | { 153 | "user": { 154 | "id":1, 155 | "name":"u1" 156 | }, 157 | "meta": { 158 | "role": "staff" 159 | } 160 | } 161 | ``` 162 | 163 | ### From Validator 164 | ```go 165 | type Auth struct{} 166 | 167 | type UserFormLogin struct { 168 | Username string `json:"username" binding:"required"` 169 | Password string `json:"password" binding:"required"` 170 | } 171 | 172 | func (Auth) Login(c *gin.Context) { 173 | var form UserFormLogin 174 | 175 | err := c.ShouldBind(&form) 176 | if err != nil { 177 | response.HandleValidatorError(c, err) 178 | return 179 | } 180 | // do login... 181 | } 182 | ``` 183 | Verification failure response, return status code `422` 184 | ```json 185 | { 186 | "message": "username is a required field", 187 | "errors": { 188 | "password": "password is a required field", 189 | "username": "username is a required field" 190 | } 191 | } 192 | ``` 193 | 194 | ### Database Transaction 195 | ```go 196 | package user 197 | 198 | import ( 199 | "context" 200 | 201 | "github.com/fitv/min/ent" 202 | "github.com/fitv/min/ent/user" 203 | "github.com/fitv/min/global" 204 | ) 205 | 206 | type User struct{} 207 | 208 | func (User) Update() { 209 | global.DB().WithTx(ctx, func(tx *ent.Tx) error { 210 | user, err := tx.User.Query().Where(user.ID(1)).ForUpdate().First(ctx) 211 | if err != nil { 212 | return err 213 | } 214 | // do update... 215 | }) 216 | } 217 | ``` 218 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | ## MIN - 一个基于 Gin 的 API 骨架 2 | 3 | [English](README.md) 4 | 5 | ## 使用的第三方包 6 | - [Gin](https://github.com/gin-gonic/gin) 7 | - [ent](https://entgo.io/ent) 8 | - [viper](https://github.com/spf13/viper) 9 | - [jwt-go](https://github.com/golang-jwt/jwt) 10 | - [go-redis](https://github.com/go-redis/redis) 11 | - [validator](https://github.com/go-playground/validator) 12 | - [swagger-ui](https://github.com/swagger-api/swagger-ui) 13 | 14 | ## 特性 15 | - 快速构建 `RESTful API` 16 | - 基于 `JWT` 的用户授权 17 | - 基于 `Viper` 的配置 18 | - 基于 `Redis` 缓存器 19 | - `ent` 模型分页器 20 | - API 资源转换器 21 | - 多语言翻译支持 22 | - Swagger UI (渲染 YAML 文件) 23 | 24 | ## 安装 25 | 克隆仓库 26 | ``` 27 | git clone https://github.com/fitv/min.git 28 | ``` 29 | 30 | 复制配置文件 31 | ``` 32 | cd min && cp config.example.yaml config.yaml 33 | ``` 34 | 35 | 运行应用 36 | ``` 37 | go run main.go 38 | ``` 39 | 40 | 构建应用 41 | ``` 42 | go build -o min main.go 43 | ``` 44 | 45 | API 文档地址 46 | ``` 47 | http://127.0.0.1:3000/apidoc 48 | ``` 49 | 50 | 运行数据库迁移 51 | ``` 52 | http://127.0.0.1:3000/api/v1/migrate 53 | ``` 54 | 55 | ## 使用说明 56 | 57 | ### 缓存 58 | ```go 59 | import "github.com/fitv/min/global" 60 | 61 | global.Cache().Set(ctx, "key", "value", time.Minute) 62 | global.Cache().Get(ctx, "key") // value 63 | global.Cache().Has(ctx, "key") // true 64 | global.Cache().TTL(ctx, "key") // time.Minute 65 | global.Cache().Del(ctx, "key") // true 66 | ``` 67 | 68 | ### 日志 69 | ```go 70 | import "github.com/fitv/min/global" 71 | 72 | global.Log().Debug("debug") 73 | global.Log().Info("info") 74 | global.Log().Warn("warn") 75 | global.Log().Error("error") 76 | global.Log().Panic("panic") 77 | global.Log().Fatal("fatal") 78 | ``` 79 | 80 | ### 多语言翻译 81 | ```go 82 | import "github.com/fitv/min/global" 83 | 84 | global.Lang().Trans("hello.world") // 世界 85 | global.Lang().Locale("en").Trans("hello.world") // world 86 | ``` 87 | 88 | ### 分页及 API 资源转换 89 | ```go 90 | package user 91 | 92 | import ( 93 | "context" 94 | "strconv" 95 | 96 | "github.com/fitv/min/app/resource" 97 | "github.com/fitv/min/core/response" 98 | "github.com/fitv/min/global" 99 | "github.com/gin-gonic/gin" 100 | ) 101 | 102 | type User struct{} 103 | 104 | // Index returns a list of users with pagination 105 | func (User) Index(c *gin.Context) { 106 | paginator, err := global.Ent().User. 107 | Query(). 108 | Paginate(ctx, c) 109 | if err != nil { 110 | response.HandleEntError(c, err) 111 | return 112 | } 113 | resource.NewUserPaginator(c, paginator).Response() 114 | } 115 | 116 | // Info returns user information 117 | func (User) Info(c *gin.Context) { 118 | id, _ := strconv.Atoi(c.Param("id")) 119 | user, err := global.Ent().User.Get(ctx, id) 120 | if err != nil { 121 | response.HandleEntError(c, err) 122 | return 123 | } 124 | resource.NewUser(c, user).Append(gin.H{ 125 | "role": "staff", 126 | }).Wrap("user").Response() 127 | } 128 | ``` 129 | 130 | 用户列表响应 131 | ```json 132 | { 133 | "current_page": 1, 134 | "per_page": 15, 135 | "last_page": 1, 136 | "total": 2, 137 | "data": [ 138 | { 139 | "id": 1, 140 | "username": "u1" 141 | }, 142 | { 143 | "id": 2, 144 | "username": "u2" 145 | } 146 | ] 147 | } 148 | ``` 149 | 150 | 用户信息响应 151 | ```json 152 | { 153 | "user": { 154 | "id":1, 155 | "name":"u1" 156 | }, 157 | "meta": { 158 | "role": "staff" 159 | } 160 | } 161 | ``` 162 | 163 | ### 表单验证 164 | ```go 165 | type Auth struct{} 166 | 167 | type UserFormLogin struct { 168 | Username string `json:"username" binding:"required"` 169 | Password string `json:"password" binding:"required"` 170 | } 171 | 172 | func (Auth) Login(c *gin.Context) { 173 | var form UserFormLogin 174 | 175 | err := c.ShouldBind(&form) 176 | if err != nil { 177 | response.HandleValidatorError(c, err) 178 | return 179 | } 180 | // do login... 181 | } 182 | ``` 183 | 验证失败响应,返回状态码`422` 184 | ```json 185 | { 186 | "message": "姓名为必填字段", 187 | "errors": { 188 | "password": "密码为必填字段", 189 | "username": "姓名为必填字段" 190 | } 191 | } 192 | ``` 193 | 194 | ### 数据库事务 195 | ```go 196 | package user 197 | 198 | import ( 199 | "context" 200 | 201 | "github.com/fitv/min/ent" 202 | "github.com/fitv/min/ent/user" 203 | "github.com/fitv/min/global" 204 | ) 205 | 206 | type User struct{} 207 | 208 | func (User) Update() { 209 | global.DB().WithTx(ctx, func(tx *ent.Tx) error { 210 | user, err := tx.User.Query().Where(user.ID(1)).ForUpdate().First(ctx) 211 | if err != nil { 212 | return err 213 | } 214 | // do update... 215 | }) 216 | } 217 | ``` 218 | -------------------------------------------------------------------------------- /app/api/v1/auth.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/fitv/min/app/resource" 7 | "github.com/fitv/min/core/auth" 8 | "github.com/fitv/min/core/response" 9 | "github.com/fitv/min/ent/user" 10 | "github.com/fitv/min/global" 11 | "github.com/fitv/min/util/hash" 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | type Auth struct{} 16 | 17 | // UserFormLogin user login form 18 | type UserFormLogin struct { 19 | Username string `json:"username" binding:"required,min=3,max=12"` 20 | Password string `json:"password" binding:"required,min=6,max=20"` 21 | } 22 | 23 | // Login user login 24 | func (Auth) Login(c *gin.Context) { 25 | var form UserFormLogin 26 | 27 | err := c.ShouldBind(&form) 28 | if err != nil { 29 | response.HandleValidatorError(c, err) 30 | return 31 | } 32 | 33 | user, err := global.Ent().User. 34 | Query(). 35 | Where(user.Username(form.Username)). 36 | First(context.Background()) 37 | if err != nil { 38 | response.HandleEntError(c, err) 39 | return 40 | } 41 | 42 | if !hash.Check(user.Password, form.Password) { 43 | response.BadRequest(c, global.Lang().Trans("auth.invalid_credentials")) 44 | return 45 | } 46 | 47 | token, err := auth.SignToken(user.ID) 48 | if err != nil { 49 | response.ServerError(c) 50 | return 51 | } 52 | response.OK(c, NewAccessToken(token)) 53 | } 54 | 55 | // Register user register 56 | func (Auth) Register(c *gin.Context) { 57 | var form UserFormLogin 58 | 59 | err := c.ShouldBind(&form) 60 | if err != nil { 61 | response.HandleValidatorError(c, err) 62 | return 63 | } 64 | 65 | user, err := global.Ent().User.Create(). 66 | SetUsername(form.Username). 67 | SetPassword(form.Password). 68 | Save(context.Background()) 69 | if err != nil { 70 | response.HandleEntError(c, err) 71 | return 72 | } 73 | resource.NewUser(c, user).Response() 74 | } 75 | 76 | // RefreshToken refresh JWT token 77 | func (Auth) Refresh(c *gin.Context) { 78 | token, err := auth.SignToken(auth.MustUID(c)) 79 | if err != nil { 80 | response.ServerError(c) 81 | return 82 | } 83 | response.OK(c, NewAccessToken(token)) 84 | } 85 | 86 | // Profile get current authorized user info 87 | func (Auth) Profile(c *gin.Context) { 88 | user, err := global.Ent().User.Get(context.Background(), auth.MustUID(c)) 89 | if err != nil { 90 | response.HandleEntError(c, err) 91 | return 92 | } 93 | resource.NewUser(c, user).Response() 94 | } 95 | -------------------------------------------------------------------------------- /app/api/v1/migrate.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/fitv/min/core/response" 7 | "github.com/fitv/min/global" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | type Migrate struct{} 12 | 13 | // Index run database migration, you should remove this method in your production environment. 14 | func (Migrate) Index(c *gin.Context) { 15 | err := global.Ent().Schema.Create( 16 | context.Background(), 17 | // schema.WithDropColumn(true), 18 | // schema.WithDropIndex(true), 19 | ) 20 | if err != nil { 21 | response.HandleEntError(c, err) 22 | return 23 | } 24 | response.OK(c, global.Lang().Trans("message.success")) 25 | } 26 | -------------------------------------------------------------------------------- /app/api/v1/v1.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/fitv/min/config" 5 | ) 6 | 7 | type AccessToken struct { 8 | AccessToken string `json:"access_token"` 9 | TokenType string `json:"token_type"` 10 | ExpiresIn int `json:"expires_in"` 11 | } 12 | 13 | func NewAccessToken(token string) *AccessToken { 14 | return &AccessToken{ 15 | AccessToken: token, 16 | TokenType: "bearer", 17 | ExpiresIn: int(config.Jwt.TTL.Seconds()), 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/middleware/auth.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/fitv/min/config" 5 | "github.com/fitv/min/core/auth" 6 | "github.com/fitv/min/core/request" 7 | "github.com/fitv/min/core/response" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func Auth() gin.HandlerFunc { 12 | return func(c *gin.Context) { 13 | claims, err := auth.VerifyToken(request.Token(c)) 14 | if err != nil { 15 | response.Unauthorized(c) 16 | return 17 | } 18 | c.Set(config.Jwt.SigningKey, claims) 19 | 20 | c.Next() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/middleware/cors.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func Cors() gin.HandlerFunc { 8 | return func(c *gin.Context) { 9 | c.Header("Access-Control-Allow-Origin", "*") 10 | c.Header("Access-Control-Allow-Credentials", "true") 11 | c.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Accept, Origin, Cache-Control, X-Requested-With") 12 | c.Header("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE") 13 | 14 | if c.Request.Method == "OPTIONS" { 15 | c.AbortWithStatus(204) 16 | return 17 | } 18 | 19 | c.Next() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/middleware/static.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func Static() gin.HandlerFunc { 8 | return func(c *gin.Context) { 9 | c.Header("Cache-Control", "public, max-age=31536000") 10 | 11 | c.Next() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/resource/user.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "github.com/fitv/min/core/resource" 5 | "github.com/fitv/min/ent" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | type User struct { 10 | user *ent.User 11 | users []*ent.User 12 | paginator *ent.Paginator 13 | } 14 | 15 | func NewUser(c *gin.Context, user *ent.User) *resource.JsonResource { 16 | return resource.NewMap(c, &User{user: user}) 17 | } 18 | 19 | func NewUsers(c *gin.Context, users []*ent.User) *resource.JsonResource { 20 | return resource.NewArray(c, &User{users: users}) 21 | } 22 | 23 | func NewUserPaginator(c *gin.Context, paginator *ent.Paginator) *resource.JsonResource { 24 | return resource.NewPaginator(c, &User{paginator: paginator}) 25 | } 26 | 27 | func (r *User) ToMap(c *gin.Context) gin.H { 28 | return gin.H{ 29 | "id": r.user.ID, 30 | "username": r.user.Username, 31 | // "tags": resource.When(r.user.Edges.Tags != nil, NewTags(c, r.user.Edges.Tags)), 32 | } 33 | } 34 | 35 | func (r *User) ToArray(c *gin.Context) []*resource.JsonResource { 36 | rs := make([]*resource.JsonResource, len(r.users)) 37 | for i, user := range r.users { 38 | rs[i] = NewUser(c, user) 39 | } 40 | return rs 41 | } 42 | 43 | func (r *User) ToPaginator(c *gin.Context) *ent.Paginator { 44 | r.paginator.Data = NewUsers(c, r.paginator.Data.([]*ent.User)) 45 | return r.paginator 46 | } 47 | -------------------------------------------------------------------------------- /app/web/apidoc.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | type ApiDoc struct{} 11 | 12 | func (ApiDoc) Index(c *gin.Context) { 13 | c.HTML(http.StatusOK, "apidoc.tmpl", gin.H{ 14 | "version": time.Now().Unix(), 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /app/web/home.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/fitv/min/config" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | type Home struct{} 11 | 12 | func (Home) Index(c *gin.Context) { 13 | c.HTML(http.StatusOK, "home.tmpl", gin.H{ 14 | "title": config.App.Name, 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /config.example.yaml: -------------------------------------------------------------------------------- 1 | app: 2 | name: "MIN" 3 | addr: "127.0.0.1" 4 | port: 3000 5 | debug: true 6 | 7 | log: 8 | level: "info" 9 | 10 | database: 11 | driver: "mysql" 12 | host: "127.0.0.1" 13 | port: 3306 14 | user: "" 15 | password: "" 16 | database: "" 17 | debug: false 18 | 19 | redis: 20 | host: "127.0.0.1" 21 | port: 6379 22 | password: "" 23 | 24 | jwt: 25 | secret: "z3OjwW1eP6wjlvkfKQrUHiaRwmY71NJvk4CPSgBavCQvANnb7lEDiifxxP50Y9Lt" 26 | -------------------------------------------------------------------------------- /config/app.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/fitv/min/core/config" 5 | ) 6 | 7 | type AppConfig struct { 8 | Name string 9 | Addr string 10 | Port int 11 | Debug bool 12 | Locale string // Supports "en", "zh" 13 | LangPath string 14 | } 15 | 16 | var App = &AppConfig{ 17 | Name: config.GetString("app.name", "MIN"), 18 | Addr: config.GetString("app.addr", "127.0.0.1"), 19 | Port: config.GetInt("app.port", 3000), 20 | Debug: config.GetBool("app.debug", false), 21 | Locale: config.GetString("app.locale", "zh"), 22 | LangPath: "locales", 23 | } 24 | -------------------------------------------------------------------------------- /config/cache.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "github.com/fitv/min/util/str" 4 | 5 | type CacheConfig struct { 6 | Driver string 7 | Prefix string 8 | Database int 9 | } 10 | 11 | var Cache = &CacheConfig{ 12 | Driver: "redis", 13 | Prefix: str.ToSnakeCase(App.Name) + ":cache:", 14 | Database: 1, 15 | } 16 | -------------------------------------------------------------------------------- /config/database.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/fitv/min/core/config" 5 | ) 6 | 7 | type DatabaseConfig struct { 8 | Driver string 9 | Host string 10 | Port int 11 | Database string 12 | User string 13 | Password string 14 | Charset string 15 | Collation string 16 | Timeout string // Dial Timeout, See https://github.com/go-sql-driver/mysql#timeout 17 | Debug bool 18 | } 19 | 20 | var Database = &DatabaseConfig{ 21 | Driver: config.GetString("database.driver", "mysql"), 22 | Host: config.GetString("database.host", "127.0.0.1"), 23 | Port: config.GetInt("database.port", 3306), 24 | Database: config.GetString("database.database"), 25 | User: config.GetString("database.user"), 26 | Password: config.GetString("database.password"), 27 | Charset: config.GetString("database.charset", "utf8mb4"), 28 | Collation: config.GetString("database.collation", "utf8mb4_unicode_ci"), 29 | Timeout: config.GetString("database.timeout", "5s"), 30 | Debug: config.GetBool("database.debug", false), 31 | } 32 | -------------------------------------------------------------------------------- /config/jwt.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/fitv/min/core/config" 7 | ) 8 | 9 | type JwtConfig struct { 10 | SigningKey string // The name of the key used to sign the token in the request context 11 | Secret string 12 | TTL time.Duration 13 | } 14 | 15 | var Jwt = &JwtConfig{ 16 | SigningKey: "jwtClaims", 17 | Secret: config.GetString("jwt.secret"), 18 | TTL: time.Hour * 1, 19 | } 20 | -------------------------------------------------------------------------------- /config/log.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/fitv/min/core/config" 5 | "github.com/fitv/min/util/str" 6 | ) 7 | 8 | type LogConfig struct { 9 | Driver string // Supports: "file" 10 | Path string 11 | Level string // Supports: "debug", "info", "warn", "error", "fatal" 12 | Daily bool // Supported driver: "file", Whether to generate a new log file every day 13 | Days int // Supported driver: "file", The number of days to keep the log file 14 | } 15 | 16 | var Log = &LogConfig{ 17 | Driver: config.GetString("log.driver", "file"), 18 | Path: config.GetString("log.path", "logs/"+str.ToSnakeCase(App.Name)+".log"), 19 | Level: config.GetString("log.level", "info"), 20 | Daily: config.GetBool("log.daily", true), 21 | Days: config.GetInt("log.days", 15), 22 | } 23 | -------------------------------------------------------------------------------- /config/redis.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "github.com/fitv/min/core/config" 4 | 5 | type RedisConfig struct { 6 | Host string 7 | Port int 8 | Password string 9 | Database int 10 | } 11 | 12 | var Redis = &RedisConfig{ 13 | Host: config.GetString("redis.host", "127.0.0.1"), 14 | Port: config.GetInt("redis.port", 6379), 15 | Password: config.GetString("redis.password", ""), 16 | Database: 0, 17 | } 18 | -------------------------------------------------------------------------------- /core/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "context" 5 | "embed" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | "time" 13 | 14 | "github.com/fitv/go-i18n" 15 | "github.com/fitv/go-logger" 16 | "github.com/fitv/min/config" 17 | "github.com/fitv/min/core/cache" 18 | "github.com/fitv/min/core/db" 19 | "github.com/fitv/min/core/redis" 20 | "github.com/gin-gonic/gin" 21 | ut "github.com/go-playground/universal-translator" 22 | "github.com/go-playground/validator/v10" 23 | ) 24 | 25 | const ( 26 | Version = "1.0.0" 27 | ) 28 | 29 | type Application struct { 30 | Gin *gin.Engine 31 | FS embed.FS 32 | DB *db.DB 33 | Cache cache.Cache 34 | Redis *redis.Redis 35 | Logger *logger.Logger 36 | Translator ut.Translator 37 | Lang *i18n.I18n 38 | Services []Service 39 | Routes []Route 40 | Validations []Validation 41 | ShutdownFuncs []func() 42 | Version string 43 | } 44 | 45 | type Route func(*gin.Engine) 46 | 47 | type Validation func(*validator.Validate, ut.Translator) 48 | 49 | type Service interface { 50 | Boot(*Application) 51 | Register(*Application) 52 | } 53 | 54 | // NewApplication create a new application 55 | func NewApplication(fs embed.FS) *Application { 56 | return &Application{ 57 | FS: fs, 58 | Version: Version, 59 | } 60 | } 61 | 62 | // Run start application 63 | func (app *Application) Run() { 64 | app.registerService() 65 | app.bootService() 66 | app.listenAndServe() 67 | } 68 | 69 | // AddShutdown add shutdown function 70 | func (app *Application) AddShutdown(fn func()) { 71 | app.ShutdownFuncs = append(app.ShutdownFuncs, fn) 72 | } 73 | 74 | // AddValidation add validation 75 | func (app *Application) AddValidation(vs ...Validation) { 76 | app.Validations = append(app.Validations, vs...) 77 | } 78 | 79 | // AddService add service 80 | func (app *Application) AddService(ss ...Service) { 81 | app.Services = append(app.Services, ss...) 82 | } 83 | 84 | // Shutdown shutdown the application 85 | func (app *Application) Shutdown() { 86 | for _, ShutdownFunc := range app.ShutdownFuncs { 87 | ShutdownFunc() 88 | } 89 | } 90 | 91 | // registerService register the application services 92 | func (app *Application) registerService() { 93 | for _, service := range app.Services { 94 | service.Register(app) 95 | } 96 | } 97 | 98 | // bootService boot the application services 99 | func (app *Application) bootService() { 100 | for _, service := range app.Services { 101 | service.Boot(app) 102 | } 103 | } 104 | 105 | // listenAndServe start http server 106 | func (app *Application) listenAndServe() { 107 | srv := &http.Server{ 108 | MaxHeaderBytes: 1 << 20, // 1MB 109 | ReadTimeout: time.Second * 15, 110 | WriteTimeout: time.Second * 15, 111 | Addr: fmt.Sprintf("%s:%d", config.App.Addr, config.App.Port), 112 | Handler: app.Gin, 113 | } 114 | 115 | go func() { 116 | if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { 117 | log.Fatalf("listen error: %s\n", err) 118 | } 119 | }() 120 | log.Printf("Server started, Listen %s\n", srv.Addr) 121 | 122 | quit := make(chan os.Signal, 1) 123 | 124 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 125 | <-quit 126 | log.Println("Shutting down server...") 127 | 128 | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) 129 | defer cancel() 130 | if err := srv.Shutdown(ctx); err != nil { 131 | log.Fatal("Server forced to shutdown: ", err) 132 | } 133 | log.Println("Server exiting") 134 | } 135 | -------------------------------------------------------------------------------- /core/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/fitv/min/config" 9 | "github.com/gin-gonic/gin" 10 | "github.com/golang-jwt/jwt/v4" 11 | "github.com/google/uuid" 12 | "github.com/spf13/cast" 13 | ) 14 | 15 | // CheckToken check the Request token 16 | func Check(c *gin.Context) (*jwt.RegisteredClaims, bool) { 17 | if claims, exist := c.Get(config.Jwt.SigningKey); exist { 18 | if claims, ok := claims.(*jwt.RegisteredClaims); ok { 19 | return claims, true 20 | } 21 | } 22 | return nil, false 23 | } 24 | 25 | // MustUID get the user ID from current request token, panic if unauthorized 26 | func MustUID(c *gin.Context) int { 27 | claims, ok := Check(c) 28 | if !ok { 29 | panic("unauthorized") 30 | } 31 | return cast.ToInt(claims.Subject) 32 | } 33 | 34 | // SignToken generate JWT token 35 | func SignToken(uid int) (string, error) { 36 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, &jwt.RegisteredClaims{ 37 | ID: uuid.NewString(), 38 | Subject: strconv.Itoa(uid), 39 | IssuedAt: jwt.NewNumericDate(time.Now()), 40 | NotBefore: jwt.NewNumericDate(time.Now()), 41 | ExpiresAt: jwt.NewNumericDate(time.Now().Add(config.Jwt.TTL)), 42 | }) 43 | return token.SignedString([]byte(config.Jwt.Secret)) 44 | } 45 | 46 | // VerifyToken verify JWT token 47 | func VerifyToken(tokenString string) (*jwt.RegisteredClaims, error) { 48 | token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) { 49 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 50 | return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) 51 | } 52 | return []byte(config.Jwt.Secret), nil 53 | }) 54 | if err != nil { 55 | return nil, err 56 | } 57 | if claims, ok := token.Claims.(*jwt.RegisteredClaims); ok && token.Valid { 58 | return claims, nil 59 | } 60 | return nil, fmt.Errorf("invalid token") 61 | } 62 | -------------------------------------------------------------------------------- /core/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | // Cache interface 9 | type Cache interface { 10 | Get(ctx context.Context, key string) (string, bool) 11 | Set(ctx context.Context, key string, value interface{}, ttl time.Duration) 12 | Has(ctx context.Context, key string) bool 13 | Del(ctx context.Context, key string) bool 14 | TTL(ctx context.Context, key string) time.Duration 15 | } 16 | 17 | // Options for cache 18 | type Option struct { 19 | Prefix string 20 | } 21 | -------------------------------------------------------------------------------- /core/cache/rediscache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/go-redis/redis/v8" 9 | ) 10 | 11 | var _ Cache = (*RedisCache)(nil) 12 | 13 | // RedisCache is a Redis-based cache. 14 | type RedisCache struct { 15 | client *redis.Client 16 | prefix string 17 | } 18 | 19 | // NewRedisCache creates a new RedisCache instance. 20 | func NewRedisCache(client *redis.Client, opt *Option) *RedisCache { 21 | return &RedisCache{ 22 | client: client, 23 | prefix: opt.Prefix, 24 | } 25 | } 26 | 27 | // Get returns the value for the given key in cache, panic when an error occurs. 28 | func (r *RedisCache) Get(ctx context.Context, key string) (string, bool) { 29 | val, err := r.client.Get(ctx, r.realKey(key)).Result() 30 | if err == redis.Nil { 31 | return "", false 32 | } 33 | if err != nil { 34 | panic(fmt.Errorf("redis get error: %w", err)) 35 | } 36 | return val, true 37 | } 38 | 39 | // Set sets the value for the given key into cache, panic when an error occurs. 40 | func (r *RedisCache) Set(ctx context.Context, key string, value interface{}, ttl time.Duration) { 41 | err := r.client.SetEX(ctx, r.realKey(key), value, ttl).Err() 42 | if err != nil { 43 | panic(fmt.Errorf("redis set error: %w", err)) 44 | } 45 | } 46 | 47 | // Has check if the cache key exists, panic when an error occurs. 48 | func (r *RedisCache) Has(ctx context.Context, key string) bool { 49 | res, err := r.client.Exists(ctx, r.realKey(key)).Result() 50 | if err != nil { 51 | panic(fmt.Errorf("redis exists error: %w", err)) 52 | } 53 | return res > 0 54 | } 55 | 56 | // TTL returns the remaining time to live of a key, panic when an error occurs. 57 | func (r *RedisCache) TTL(ctx context.Context, key string) time.Duration { 58 | ttl, err := r.client.TTL(ctx, r.realKey(key)).Result() 59 | if err != nil { 60 | panic(fmt.Errorf("redis ttl error: %w", err)) 61 | } 62 | if ttl < 0 { 63 | return 0 64 | } 65 | return ttl 66 | } 67 | 68 | // Del deletes the given key, panic when an error occurs. 69 | func (r *RedisCache) Del(ctx context.Context, key string) bool { 70 | val, err := r.client.Del(ctx, r.realKey(key)).Result() 71 | if err != nil { 72 | panic(fmt.Errorf("redis del error: %w", err)) 73 | } 74 | return val > 0 75 | } 76 | 77 | // realKey returns the key with prefix. 78 | func (r *RedisCache) realKey(key string) string { 79 | return r.prefix + key 80 | } 81 | -------------------------------------------------------------------------------- /core/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cast" 7 | "github.com/spf13/viper" 8 | ) 9 | 10 | var v *viper.Viper 11 | 12 | func init() { 13 | v = viper.New() 14 | 15 | v.SetConfigName("config") 16 | v.SetConfigType("yaml") 17 | v.AddConfigPath(".") 18 | 19 | err := v.ReadInConfig() 20 | if err != nil { 21 | panic(fmt.Errorf("read config file error: %w", err)) 22 | } 23 | } 24 | 25 | // Set sets the value for the key in the config. 26 | func Set(key string, value interface{}) { 27 | v.Set(key, value) 28 | } 29 | 30 | // Get gets the value for the key in the config. 31 | func Get(key string, defaultValue ...interface{}) interface{} { 32 | if v.IsSet(key) { 33 | return v.Get(key) 34 | } 35 | 36 | if len(defaultValue) > 0 { 37 | return defaultValue[0] 38 | } 39 | return nil 40 | } 41 | 42 | // GetStringSlice gets the string value for the key in the config. 43 | func GetString(key string, defaultValue ...interface{}) string { 44 | return cast.ToString(Get(key, defaultValue...)) 45 | } 46 | 47 | // GetInt gets the int value for the key in the config. 48 | func GetInt(key string, defaultValue ...interface{}) int { 49 | return cast.ToInt(Get(key, defaultValue...)) 50 | } 51 | 52 | // GetBool gets the bool value for the key in the config. 53 | func GetBool(key string, defaultValue ...interface{}) bool { 54 | return cast.ToBool(Get(key, defaultValue...)) 55 | } 56 | -------------------------------------------------------------------------------- /core/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/fitv/min/ent" 8 | _ "github.com/go-sql-driver/mysql" 9 | ) 10 | 11 | // DB is a wrapper around *ent.Client. 12 | type DB struct { 13 | client *ent.Client 14 | } 15 | 16 | // TxFunc is the function type used by WithTx. 17 | type TxFunc func(tx *ent.Tx) error 18 | 19 | // Option represents the options for the DB. 20 | type Option struct { 21 | Driver string 22 | Dns string 23 | Debug bool 24 | } 25 | 26 | // New returns a new DB instance. 27 | func New(opt *Option) (*DB, error) { 28 | var entOptions []ent.Option 29 | if opt.Debug { 30 | entOptions = append(entOptions, ent.Debug()) 31 | } 32 | db := &DB{} 33 | client, err := ent.Open(opt.Driver, opt.Dns, entOptions...) 34 | if err != nil { 35 | return nil, err 36 | } 37 | db.client = client 38 | return db, nil 39 | } 40 | 41 | // Client returns the underlying *ent.Client. 42 | func (db *DB) Client() *ent.Client { 43 | return db.client 44 | } 45 | 46 | // Close closes the database client. 47 | func (db *DB) Close() error { 48 | return db.client.Close() 49 | } 50 | 51 | // WithTx runs the given function in a transaction. 52 | func (db *DB) WithTx(ctx context.Context, fn TxFunc) error { 53 | tx, err := db.client.Tx(ctx) 54 | if err != nil { 55 | return err 56 | } 57 | defer func() { 58 | if v := recover(); v != nil { 59 | tx.Rollback() 60 | panic(v) 61 | } 62 | }() 63 | 64 | if err := fn(tx); err != nil { 65 | if rerr := tx.Rollback(); rerr != nil { 66 | err = fmt.Errorf("rolling back transaction: %w", rerr) 67 | } 68 | return err 69 | } 70 | 71 | if err := tx.Commit(); err != nil { 72 | return fmt.Errorf("committing transaction: %w", err) 73 | } 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /core/redis/redis.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/go-redis/redis/v8" 8 | ) 9 | 10 | // Redis is a Redis client wrapper. 11 | type Redis struct { 12 | client *redis.Client 13 | } 14 | 15 | // Option is the Redis option. 16 | type Option struct { 17 | Addr string 18 | Password string 19 | DB int 20 | } 21 | 22 | // New returns a new Redis client. 23 | func New(opt *Option) (*Redis, error) { 24 | client := redis.NewClient(&redis.Options{ 25 | Addr: opt.Addr, 26 | Password: opt.Password, 27 | DB: opt.DB, 28 | }) 29 | 30 | err := client.Ping(context.Background()).Err() 31 | if err != nil { 32 | return nil, fmt.Errorf("redis ping error: %w", err) 33 | } 34 | return &Redis{client: client}, nil 35 | } 36 | 37 | // Client returns the Redis client. 38 | func (r *Redis) Client() *redis.Client { 39 | return r.client 40 | } 41 | 42 | // Close closes the Redis client. 43 | func (r *Redis) Close() error { 44 | return r.client.Close() 45 | } 46 | -------------------------------------------------------------------------------- /core/request/request.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | // Token returns the token from the request header or query. 10 | func Token(c *gin.Context) string { 11 | token := c.GetHeader("Authorization") 12 | splits := strings.SplitN(strings.TrimSpace(token), " ", 2) 13 | if len(splits) == 2 { 14 | return splits[1] 15 | } 16 | return c.Query("token") 17 | } 18 | 19 | // IsApiRoute determines whether the request path is an api route. 20 | func IsApiRoute(c *gin.Context) bool { 21 | return strings.HasPrefix(c.Request.URL.Path, "/api/") 22 | } 23 | -------------------------------------------------------------------------------- /core/resource/resource.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/fitv/min/core/response" 7 | "github.com/fitv/min/ent" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | const ( 12 | TypeMap = iota 13 | TypeArray 14 | TypePaginator 15 | ) 16 | 17 | var ( 18 | DefaultWrapKey = "data" 19 | DefaultAppendKey = "meta" 20 | ) 21 | 22 | type Resource interface { 23 | ToMap(*gin.Context) gin.H 24 | ToArray(*gin.Context) []*JsonResource 25 | ToPaginator(*gin.Context) *ent.Paginator 26 | } 27 | 28 | // JsonResource is a resource that can be marshalled to JSON. 29 | type JsonResource struct { 30 | ctx *gin.Context 31 | resource Resource 32 | resourceType int 33 | append gin.H 34 | wrap string 35 | } 36 | 37 | // MissingValue is a placeholder for a missing value. 38 | type MissingValue struct{} 39 | 40 | // NewMap returns a new JsonResource with Map type 41 | func NewMap(c *gin.Context, resource Resource) *JsonResource { 42 | return &JsonResource{ctx: c, resource: resource, resourceType: TypeMap} 43 | } 44 | 45 | // NewArray returns a new JsonResource with Array type 46 | func NewArray(c *gin.Context, resource Resource) *JsonResource { 47 | return &JsonResource{ctx: c, resource: resource, resourceType: TypeArray} 48 | } 49 | 50 | // NewPaginator returns a new JsonResource with Paginator type 51 | func NewPaginator(c *gin.Context, resource Resource) *JsonResource { 52 | return &JsonResource{ctx: c, resource: resource, resourceType: TypePaginator} 53 | } 54 | 55 | // Append adds a new key-value pair to the resource 56 | func (r *JsonResource) Append(obj gin.H) *JsonResource { 57 | r.append = obj 58 | return r 59 | } 60 | 61 | // Warp sets the wrap name for the resource 62 | func (r *JsonResource) Wrap(name string) *JsonResource { 63 | r.wrap = name 64 | return r 65 | } 66 | 67 | // resolve returns the resource value 68 | func (r *JsonResource) resolve() interface{} { 69 | switch r.resourceType { 70 | case TypeMap: 71 | return r.filter(r.resource.ToMap(r.ctx)) 72 | case TypeArray: 73 | return r.resource.ToArray(r.ctx) 74 | case TypePaginator: 75 | return r.resource.ToPaginator(r.ctx) 76 | default: 77 | return r.resource 78 | } 79 | } 80 | 81 | // filter returns the resource value after applying the filters 82 | func (JsonResource) filter(dict gin.H) gin.H { 83 | data := gin.H{} 84 | 85 | for key, value := range dict { 86 | switch v := value.(type) { 87 | case *JsonResource: 88 | data[key] = v.resolve() 89 | case *MissingValue: 90 | // skip 91 | default: 92 | data[key] = value 93 | } 94 | } 95 | return data 96 | } 97 | 98 | // MarshalJSON implements the json.Marshaler interface. 99 | func (r *JsonResource) MarshalJSON() ([]byte, error) { 100 | return json.Marshal(r.resolve()) 101 | } 102 | 103 | // Response returns the resource as a response. 104 | func (r *JsonResource) Response() { 105 | obj := r.resolve() 106 | 107 | wrapKey := DefaultWrapKey 108 | if len(r.wrap) > 0 { 109 | wrapKey = r.wrap 110 | } 111 | 112 | if r.append != nil { 113 | obj = gin.H{ 114 | wrapKey: obj, 115 | DefaultAppendKey: r.append, 116 | } 117 | } 118 | response.OK(r.ctx, obj) 119 | } 120 | 121 | // When determines if the given condition is true then return the value or executes the given callback. 122 | func When(ok bool, value interface{}) interface{} { 123 | if !ok { 124 | return &MissingValue{} 125 | } 126 | if fn, ok := value.(func() interface{}); ok { 127 | return fn() 128 | } 129 | return value 130 | } 131 | -------------------------------------------------------------------------------- /core/response/response.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "regexp" 7 | "strings" 8 | 9 | "github.com/fitv/min/ent" 10 | "github.com/fitv/min/global" 11 | "github.com/fitv/min/util/str" 12 | "github.com/gin-gonic/gin" 13 | "github.com/go-playground/validator/v10" 14 | ) 15 | 16 | var ( 17 | // regexEntNotFoundLabel match ent label from ent not found error 18 | regexEntNotFoundLabel = regexp.MustCompile(`^ent: (\w+) not found$`) 19 | // regexValidatorLabel match validator label from struct name, eg: "xxFrom", "xxFromFoo" 20 | regexValidatorLabel = regexp.MustCompile(`^(\w+)Form`) 21 | ) 22 | 23 | func OK(c *gin.Context, obj interface{}) { 24 | if message, ok := obj.(string); ok { 25 | obj = gin.H{ 26 | "message": message, 27 | } 28 | } 29 | c.AbortWithStatusJSON(http.StatusOK, obj) 30 | } 31 | 32 | func BadRequest(c *gin.Context, message string) { 33 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{ 34 | "message": message, 35 | }) 36 | } 37 | 38 | func Unauthorized(c *gin.Context) { 39 | c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ 40 | "message": global.Lang().Trans("message.unauthorized"), 41 | }) 42 | } 43 | 44 | func Forbidden(c *gin.Context) { 45 | c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ 46 | "message": global.Lang().Trans("message.forbidden"), 47 | }) 48 | } 49 | 50 | func NotFound(c *gin.Context, message string) { 51 | c.AbortWithStatusJSON(http.StatusNotFound, gin.H{ 52 | "message": message, 53 | }) 54 | } 55 | 56 | func UnprocessableEntity(c *gin.Context, message string, errors map[string]string) { 57 | c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{ 58 | "message": message, 59 | "errors": errors, 60 | }) 61 | } 62 | 63 | func ServerError(c *gin.Context) { 64 | c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ 65 | "message": global.Lang().Trans("message.server_error"), 66 | }) 67 | } 68 | 69 | // HandleEntError handle Ent error 70 | func HandleEntError(c *gin.Context, err error) { 71 | switch err.(type) { 72 | case *ent.NotFoundError: 73 | label := "message" 74 | matches := regexEntNotFoundLabel.FindStringSubmatch(err.Error()) 75 | if len(matches) > 1 { 76 | label = matches[1] 77 | } 78 | NotFound(c, global.Lang().Trans(label+".not_found")) 79 | default: 80 | global.Log().Error(fmt.Errorf("ent error: %w", err)) 81 | ServerError(c) 82 | } 83 | } 84 | 85 | // HandleValidatorError handle Validator error 86 | func HandleValidatorError(c *gin.Context, err error) { 87 | errs, ok := err.(validator.ValidationErrors) 88 | if !ok || len(errs) == 0 { 89 | BadRequest(c, global.Lang().Trans("message.validate_failed")) 90 | return 91 | } 92 | 93 | var label, message string 94 | errors := make(map[string]string) 95 | matches := regexValidatorLabel.FindStringSubmatch(errs[0].StructNamespace()) 96 | if len(matches) > 1 { 97 | label = str.ToSnakeCase(matches[1]) 98 | } 99 | 100 | for i, err := range errs { 101 | field := str.ToSnakeCase(err.Field()) 102 | name := global.Lang().Trans(label + "." + field) 103 | 104 | errors[field] = strings.Replace(err.Translate(global.Trans()), err.Field(), name, 1) 105 | if i == 0 { 106 | message = errors[field] 107 | } 108 | } 109 | UnprocessableEntity(c, message, errors) 110 | } 111 | -------------------------------------------------------------------------------- /core/service/cache.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/fitv/min/config" 7 | "github.com/fitv/min/core/app" 8 | "github.com/fitv/min/core/cache" 9 | "github.com/fitv/min/core/redis" 10 | ) 11 | 12 | type Cache struct { 13 | Service 14 | } 15 | 16 | func (Cache) Register(app *app.Application) { 17 | switch config.Cache.Driver { 18 | case "redis": 19 | redis, err := redis.New(&redis.Option{ 20 | Addr: fmt.Sprintf("%s:%d", config.Redis.Host, config.Redis.Port), 21 | Password: config.Redis.Password, 22 | DB: config.Cache.Database, 23 | }) 24 | if err != nil { 25 | panic(fmt.Errorf("redis init error: %w", err)) 26 | } 27 | 28 | app.AddShutdown(func() { 29 | redis.Close() 30 | }) 31 | app.Cache = cache.NewRedisCache(redis.Client(), &cache.Option{ 32 | Prefix: config.Cache.Prefix, 33 | }) 34 | default: 35 | panic(fmt.Errorf("unsupported cache driver: %s", config.Cache.Driver)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/service/database.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/fitv/min/config" 7 | "github.com/fitv/min/core/app" 8 | "github.com/fitv/min/core/db" 9 | ) 10 | 11 | type Database struct { 12 | Service 13 | } 14 | 15 | func (Database) Register(app *app.Application) { 16 | dns := fmt.Sprintf( 17 | "%s:%s@tcp(%s:%d)/%s?charset=%s&collation=%s&timeout=%s&parseTime=True&loc=Local", 18 | config.Database.User, 19 | config.Database.Password, 20 | config.Database.Host, 21 | config.Database.Port, 22 | config.Database.Database, 23 | config.Database.Charset, 24 | config.Database.Collation, 25 | config.Database.Timeout, 26 | ) 27 | 28 | switch config.Database.Driver { 29 | case "mysql": 30 | db, err := db.New(&db.Option{ 31 | Dns: dns, 32 | Driver: config.Database.Driver, 33 | Debug: config.Database.Debug, 34 | }) 35 | if err != nil { 36 | panic(fmt.Errorf("database init error: %w", err)) 37 | } 38 | app.DB = db 39 | 40 | app.AddShutdown(func() { 41 | app.DB.Close() 42 | }) 43 | default: 44 | panic(fmt.Errorf("unsupported database driver: %s", config.Database.Driver)) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /core/service/gin.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/fitv/min/config" 11 | "github.com/fitv/min/core/app" 12 | "github.com/fitv/min/core/request" 13 | "github.com/fitv/min/core/response" 14 | "github.com/gin-gonic/gin" 15 | ) 16 | 17 | type Gin struct { 18 | Service 19 | } 20 | 21 | func (Gin) Register(app *app.Application) { 22 | if !config.App.Debug { 23 | gin.DisableConsoleColor() 24 | gin.SetMode(gin.ReleaseMode) 25 | 26 | file, err := os.OpenFile(filepath.Dir(config.Log.Path)+"/gin.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644) 27 | if err != nil { 28 | panic(fmt.Errorf("open log file error: %w", err)) 29 | } 30 | app.AddShutdown(func() { 31 | file.Close() 32 | }) 33 | 34 | gin.DefaultWriter = io.MultiWriter(file) 35 | gin.DefaultErrorWriter = io.MultiWriter(file) 36 | } 37 | 38 | app.Gin = gin.New() 39 | 40 | // Register Logger and Recovery middleware 41 | app.Gin.Use(gin.Logger(), gin.CustomRecovery(func(c *gin.Context, err interface{}) { 42 | if request.IsApiRoute(c) { 43 | response.ServerError(c) 44 | return 45 | } 46 | c.HTML(http.StatusInternalServerError, "500.tmpl", gin.H{}) 47 | c.Abort() 48 | })) 49 | } 50 | -------------------------------------------------------------------------------- /core/service/logger.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/fitv/go-logger" 8 | "github.com/fitv/min/config" 9 | "github.com/fitv/min/core/app" 10 | ) 11 | 12 | type Logger struct { 13 | Service 14 | } 15 | 16 | func (Logger) Register(app *app.Application) { 17 | logLevel := logger.InfoLevel 18 | for key, val := range logger.LevelMap { 19 | if val == strings.ToLower(config.Log.Level) { 20 | logLevel = key 21 | } 22 | } 23 | option := &logger.Option{ 24 | Path: config.Log.Path, 25 | Daily: config.Log.Daily, 26 | Days: config.Log.Days, 27 | } 28 | 29 | switch config.Log.Driver { 30 | case "file": 31 | fileWriter := logger.NewFileWriter(option) 32 | app.Logger = logger.New() 33 | app.Logger.SetOutput(fileWriter) 34 | app.Logger.SetLevel(logLevel) 35 | 36 | app.AddShutdown(func() { 37 | fileWriter.Close() 38 | }) 39 | default: 40 | panic(fmt.Errorf("logger driver %s not support", config.Log.Driver)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /core/service/redis.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/fitv/min/config" 7 | "github.com/fitv/min/core/app" 8 | "github.com/fitv/min/core/redis" 9 | ) 10 | 11 | type Redis struct { 12 | Service 13 | } 14 | 15 | func (Redis) Register(app *app.Application) { 16 | var err error 17 | 18 | app.Redis, err = redis.New(&redis.Option{ 19 | Addr: fmt.Sprintf("%s:%d", config.Redis.Host, config.Redis.Port), 20 | Password: config.Redis.Password, 21 | DB: config.Redis.Database, 22 | }) 23 | if err != nil { 24 | panic(fmt.Errorf("redis init error: %w", err)) 25 | } 26 | 27 | app.AddShutdown(func() { 28 | app.Redis.Close() 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /core/service/router.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/fitv/min/core/app" 7 | "github.com/fitv/min/core/request" 8 | "github.com/fitv/min/core/response" 9 | "github.com/fitv/min/routes" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | type Router struct { 14 | Service 15 | } 16 | 17 | func (Router) Boot(app *app.Application) { 18 | routes.Web(app.Gin) 19 | 20 | routes.Api(app.Gin) 21 | 22 | // Register the Not Found handler. 23 | app.Gin.NoRoute(func(c *gin.Context) { 24 | if request.IsApiRoute(c) { 25 | response.NotFound(c, app.Lang.Trans("message.not_found")) 26 | return 27 | } 28 | c.HTML(http.StatusNotFound, "404.tmpl", gin.H{}) 29 | c.Abort() 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /core/service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import "github.com/fitv/min/core/app" 4 | 5 | var _ app.Service = (*Service)(nil) 6 | 7 | type Service struct{} 8 | 9 | func (Service) Register(*app.Application) {} 10 | func (Service) Boot(*app.Application) {} 11 | -------------------------------------------------------------------------------- /core/service/translator.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/fitv/go-i18n" 7 | "github.com/fitv/min/config" 8 | "github.com/fitv/min/core/app" 9 | "github.com/gin-gonic/gin/binding" 10 | "github.com/go-playground/locales/en" 11 | "github.com/go-playground/locales/zh" 12 | ut "github.com/go-playground/universal-translator" 13 | "github.com/go-playground/validator/v10" 14 | en_translations "github.com/go-playground/validator/v10/translations/en" 15 | zh_translations "github.com/go-playground/validator/v10/translations/zh" 16 | ) 17 | 18 | type Translator struct { 19 | Service 20 | } 21 | 22 | func (Translator) Register(app *app.Application) { 23 | var err error 24 | var trans ut.Translator 25 | validate := binding.Validator.Engine().(*validator.Validate) 26 | 27 | app.Lang, err = i18n.New(app.FS, config.App.LangPath) 28 | if err != nil { 29 | panic(fmt.Errorf("i18n init error: %w", err)) 30 | } 31 | app.Lang.SetDefaultLocale(config.App.Locale) 32 | 33 | switch config.App.Locale { 34 | case "en": 35 | en := en.New() 36 | uni := ut.New(en, en) 37 | trans, _ = uni.GetTranslator("en") 38 | for _, validation := range app.Validations { 39 | validation(validate, trans) 40 | } 41 | en_translations.RegisterDefaultTranslations(validate, trans) 42 | case "zh": 43 | en := en.New() 44 | zh := zh.New() 45 | uni := ut.New(en, zh, en) 46 | trans, _ = uni.GetTranslator("zh") 47 | for _, validation := range app.Validations { 48 | validation(validate, trans) 49 | } 50 | zh_translations.RegisterDefaultTranslations(validate, trans) 51 | default: 52 | panic(fmt.Errorf("unsupported locale: %s", config.App.Locale)) 53 | } 54 | 55 | app.Translator = trans 56 | } 57 | -------------------------------------------------------------------------------- /core/service/validator.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/fitv/min/core/app" 7 | ut "github.com/go-playground/universal-translator" 8 | "github.com/go-playground/validator/v10" 9 | ) 10 | 11 | var ( 12 | regexpMobile = regexp.MustCompile("^1[3-9][0-9]{9}$") 13 | ) 14 | 15 | type Validator struct { 16 | Service 17 | } 18 | 19 | func (Validator) Register(app *app.Application) { 20 | app.AddValidation(func(validate *validator.Validate, trans ut.Translator) { 21 | validate.RegisterValidation("mobile", func(fl validator.FieldLevel) bool { 22 | return regexpMobile.MatchString(fl.Field().Interface().(string)) 23 | }) 24 | 25 | validate.RegisterTranslation("mobile", trans, func(ut ut.Translator) error { 26 | return ut.Add("mobile", app.Lang.Trans("validation.mobile"), false) 27 | }, func(ut ut.Translator, fe validator.FieldError) string { 28 | t, _ := ut.T("mobile", fe.Field()) 29 | return t 30 | }) 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /ent/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "log" 9 | 10 | "github.com/fitv/min/ent/migrate" 11 | 12 | "github.com/fitv/min/ent/user" 13 | 14 | "entgo.io/ent/dialect" 15 | "entgo.io/ent/dialect/sql" 16 | ) 17 | 18 | // Client is the client that holds all ent builders. 19 | type Client struct { 20 | config 21 | // Schema is the client for creating, migrating and dropping schema. 22 | Schema *migrate.Schema 23 | // User is the client for interacting with the User builders. 24 | User *UserClient 25 | } 26 | 27 | // NewClient creates a new client configured with the given options. 28 | func NewClient(opts ...Option) *Client { 29 | cfg := config{log: log.Println, hooks: &hooks{}} 30 | cfg.options(opts...) 31 | client := &Client{config: cfg} 32 | client.init() 33 | return client 34 | } 35 | 36 | func (c *Client) init() { 37 | c.Schema = migrate.NewSchema(c.driver) 38 | c.User = NewUserClient(c.config) 39 | } 40 | 41 | // Open opens a database/sql.DB specified by the driver name and 42 | // the data source name, and returns a new client attached to it. 43 | // Optional parameters can be added for configuring the client. 44 | func Open(driverName, dataSourceName string, options ...Option) (*Client, error) { 45 | switch driverName { 46 | case dialect.MySQL, dialect.Postgres, dialect.SQLite: 47 | drv, err := sql.Open(driverName, dataSourceName) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return NewClient(append(options, Driver(drv))...), nil 52 | default: 53 | return nil, fmt.Errorf("unsupported driver: %q", driverName) 54 | } 55 | } 56 | 57 | // Tx returns a new transactional client. The provided context 58 | // is used until the transaction is committed or rolled back. 59 | func (c *Client) Tx(ctx context.Context) (*Tx, error) { 60 | if _, ok := c.driver.(*txDriver); ok { 61 | return nil, fmt.Errorf("ent: cannot start a transaction within a transaction") 62 | } 63 | tx, err := newTx(ctx, c.driver) 64 | if err != nil { 65 | return nil, fmt.Errorf("ent: starting a transaction: %w", err) 66 | } 67 | cfg := c.config 68 | cfg.driver = tx 69 | return &Tx{ 70 | ctx: ctx, 71 | config: cfg, 72 | User: NewUserClient(cfg), 73 | }, nil 74 | } 75 | 76 | // BeginTx returns a transactional client with specified options. 77 | func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { 78 | if _, ok := c.driver.(*txDriver); ok { 79 | return nil, fmt.Errorf("ent: cannot start a transaction within a transaction") 80 | } 81 | tx, err := c.driver.(interface { 82 | BeginTx(context.Context, *sql.TxOptions) (dialect.Tx, error) 83 | }).BeginTx(ctx, opts) 84 | if err != nil { 85 | return nil, fmt.Errorf("ent: starting a transaction: %w", err) 86 | } 87 | cfg := c.config 88 | cfg.driver = &txDriver{tx: tx, drv: c.driver} 89 | return &Tx{ 90 | config: cfg, 91 | User: NewUserClient(cfg), 92 | }, nil 93 | } 94 | 95 | // Debug returns a new debug-client. It's used to get verbose logging on specific operations. 96 | // 97 | // client.Debug(). 98 | // User. 99 | // Query(). 100 | // Count(ctx) 101 | // 102 | func (c *Client) Debug() *Client { 103 | if c.debug { 104 | return c 105 | } 106 | cfg := c.config 107 | cfg.driver = dialect.Debug(c.driver, c.log) 108 | client := &Client{config: cfg} 109 | client.init() 110 | return client 111 | } 112 | 113 | // Close closes the database connection and prevents new queries from starting. 114 | func (c *Client) Close() error { 115 | return c.driver.Close() 116 | } 117 | 118 | // Use adds the mutation hooks to all the entity clients. 119 | // In order to add hooks to a specific client, call: `client.Node.Use(...)`. 120 | func (c *Client) Use(hooks ...Hook) { 121 | c.User.Use(hooks...) 122 | } 123 | 124 | // UserClient is a client for the User schema. 125 | type UserClient struct { 126 | config 127 | } 128 | 129 | // NewUserClient returns a client for the User from the given config. 130 | func NewUserClient(c config) *UserClient { 131 | return &UserClient{config: c} 132 | } 133 | 134 | // Use adds a list of mutation hooks to the hooks stack. 135 | // A call to `Use(f, g, h)` equals to `user.Hooks(f(g(h())))`. 136 | func (c *UserClient) Use(hooks ...Hook) { 137 | c.hooks.User = append(c.hooks.User, hooks...) 138 | } 139 | 140 | // Create returns a create builder for User. 141 | func (c *UserClient) Create() *UserCreate { 142 | mutation := newUserMutation(c.config, OpCreate) 143 | return &UserCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} 144 | } 145 | 146 | // CreateBulk returns a builder for creating a bulk of User entities. 147 | func (c *UserClient) CreateBulk(builders ...*UserCreate) *UserCreateBulk { 148 | return &UserCreateBulk{config: c.config, builders: builders} 149 | } 150 | 151 | // Update returns an update builder for User. 152 | func (c *UserClient) Update() *UserUpdate { 153 | mutation := newUserMutation(c.config, OpUpdate) 154 | return &UserUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} 155 | } 156 | 157 | // UpdateOne returns an update builder for the given entity. 158 | func (c *UserClient) UpdateOne(u *User) *UserUpdateOne { 159 | mutation := newUserMutation(c.config, OpUpdateOne, withUser(u)) 160 | return &UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} 161 | } 162 | 163 | // UpdateOneID returns an update builder for the given id. 164 | func (c *UserClient) UpdateOneID(id int) *UserUpdateOne { 165 | mutation := newUserMutation(c.config, OpUpdateOne, withUserID(id)) 166 | return &UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} 167 | } 168 | 169 | // Delete returns a delete builder for User. 170 | func (c *UserClient) Delete() *UserDelete { 171 | mutation := newUserMutation(c.config, OpDelete) 172 | return &UserDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} 173 | } 174 | 175 | // DeleteOne returns a delete builder for the given entity. 176 | func (c *UserClient) DeleteOne(u *User) *UserDeleteOne { 177 | return c.DeleteOneID(u.ID) 178 | } 179 | 180 | // DeleteOneID returns a delete builder for the given id. 181 | func (c *UserClient) DeleteOneID(id int) *UserDeleteOne { 182 | builder := c.Delete().Where(user.ID(id)) 183 | builder.mutation.id = &id 184 | builder.mutation.op = OpDeleteOne 185 | return &UserDeleteOne{builder} 186 | } 187 | 188 | // Query returns a query builder for User. 189 | func (c *UserClient) Query() *UserQuery { 190 | return &UserQuery{ 191 | config: c.config, 192 | } 193 | } 194 | 195 | // Get returns a User entity by its id. 196 | func (c *UserClient) Get(ctx context.Context, id int) (*User, error) { 197 | return c.Query().Where(user.ID(id)).Only(ctx) 198 | } 199 | 200 | // GetX is like Get, but panics if an error occurs. 201 | func (c *UserClient) GetX(ctx context.Context, id int) *User { 202 | obj, err := c.Get(ctx, id) 203 | if err != nil { 204 | panic(err) 205 | } 206 | return obj 207 | } 208 | 209 | // Hooks returns the client hooks. 210 | func (c *UserClient) Hooks() []Hook { 211 | hooks := c.hooks.User 212 | return append(hooks[:len(hooks):len(hooks)], user.Hooks[:]...) 213 | } 214 | -------------------------------------------------------------------------------- /ent/config.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "entgo.io/ent" 7 | "entgo.io/ent/dialect" 8 | ) 9 | 10 | // Option function to configure the client. 11 | type Option func(*config) 12 | 13 | // Config is the configuration for the client and its builder. 14 | type config struct { 15 | // driver used for executing database requests. 16 | driver dialect.Driver 17 | // debug enable a debug logging. 18 | debug bool 19 | // log used for logging on debug mode. 20 | log func(...interface{}) 21 | // hooks to execute on mutations. 22 | hooks *hooks 23 | } 24 | 25 | // hooks per client, for fast access. 26 | type hooks struct { 27 | User []ent.Hook 28 | } 29 | 30 | // Options applies the options on the config object. 31 | func (c *config) options(opts ...Option) { 32 | for _, opt := range opts { 33 | opt(c) 34 | } 35 | if c.debug { 36 | c.driver = dialect.Debug(c.driver, c.log) 37 | } 38 | } 39 | 40 | // Debug enables debug logging on the ent.Driver. 41 | func Debug() Option { 42 | return func(c *config) { 43 | c.debug = true 44 | } 45 | } 46 | 47 | // Log sets the logging function for debug mode. 48 | func Log(fn func(...interface{})) Option { 49 | return func(c *config) { 50 | c.log = fn 51 | } 52 | } 53 | 54 | // Driver configures the client driver. 55 | func Driver(driver dialect.Driver) Option { 56 | return func(c *config) { 57 | c.driver = driver 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ent/context.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | type clientCtxKey struct{} 10 | 11 | // FromContext returns a Client stored inside a context, or nil if there isn't one. 12 | func FromContext(ctx context.Context) *Client { 13 | c, _ := ctx.Value(clientCtxKey{}).(*Client) 14 | return c 15 | } 16 | 17 | // NewContext returns a new context with the given Client attached. 18 | func NewContext(parent context.Context, c *Client) context.Context { 19 | return context.WithValue(parent, clientCtxKey{}, c) 20 | } 21 | 22 | type txCtxKey struct{} 23 | 24 | // TxFromContext returns a Tx stored inside a context, or nil if there isn't one. 25 | func TxFromContext(ctx context.Context) *Tx { 26 | tx, _ := ctx.Value(txCtxKey{}).(*Tx) 27 | return tx 28 | } 29 | 30 | // NewTxContext returns a new context with the given Tx attached. 31 | func NewTxContext(parent context.Context, tx *Tx) context.Context { 32 | return context.WithValue(parent, txCtxKey{}, tx) 33 | } 34 | -------------------------------------------------------------------------------- /ent/ent.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | 9 | "entgo.io/ent" 10 | "entgo.io/ent/dialect/sql" 11 | "github.com/fitv/min/ent/user" 12 | ) 13 | 14 | // ent aliases to avoid import conflicts in user's code. 15 | type ( 16 | Op = ent.Op 17 | Hook = ent.Hook 18 | Value = ent.Value 19 | Query = ent.Query 20 | Policy = ent.Policy 21 | Mutator = ent.Mutator 22 | Mutation = ent.Mutation 23 | MutateFunc = ent.MutateFunc 24 | ) 25 | 26 | // OrderFunc applies an ordering on the sql selector. 27 | type OrderFunc func(*sql.Selector) 28 | 29 | // columnChecker returns a function indicates if the column exists in the given column. 30 | func columnChecker(table string) func(string) error { 31 | checks := map[string]func(string) bool{ 32 | user.Table: user.ValidColumn, 33 | } 34 | check, ok := checks[table] 35 | if !ok { 36 | return func(string) error { 37 | return fmt.Errorf("unknown table %q", table) 38 | } 39 | } 40 | return func(column string) error { 41 | if !check(column) { 42 | return fmt.Errorf("unknown column %q for table %q", column, table) 43 | } 44 | return nil 45 | } 46 | } 47 | 48 | // Asc applies the given fields in ASC order. 49 | func Asc(fields ...string) OrderFunc { 50 | return func(s *sql.Selector) { 51 | check := columnChecker(s.TableName()) 52 | for _, f := range fields { 53 | if err := check(f); err != nil { 54 | s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)}) 55 | } 56 | s.OrderBy(sql.Asc(s.C(f))) 57 | } 58 | } 59 | } 60 | 61 | // Desc applies the given fields in DESC order. 62 | func Desc(fields ...string) OrderFunc { 63 | return func(s *sql.Selector) { 64 | check := columnChecker(s.TableName()) 65 | for _, f := range fields { 66 | if err := check(f); err != nil { 67 | s.AddError(&ValidationError{Name: f, err: fmt.Errorf("ent: %w", err)}) 68 | } 69 | s.OrderBy(sql.Desc(s.C(f))) 70 | } 71 | } 72 | } 73 | 74 | // AggregateFunc applies an aggregation step on the group-by traversal/selector. 75 | type AggregateFunc func(*sql.Selector) string 76 | 77 | // As is a pseudo aggregation function for renaming another other functions with custom names. For example: 78 | // 79 | // GroupBy(field1, field2). 80 | // Aggregate(ent.As(ent.Sum(field1), "sum_field1"), (ent.As(ent.Sum(field2), "sum_field2")). 81 | // Scan(ctx, &v) 82 | // 83 | func As(fn AggregateFunc, end string) AggregateFunc { 84 | return func(s *sql.Selector) string { 85 | return sql.As(fn(s), end) 86 | } 87 | } 88 | 89 | // Count applies the "count" aggregation function on each group. 90 | func Count() AggregateFunc { 91 | return func(s *sql.Selector) string { 92 | return sql.Count("*") 93 | } 94 | } 95 | 96 | // Max applies the "max" aggregation function on the given field of each group. 97 | func Max(field string) AggregateFunc { 98 | return func(s *sql.Selector) string { 99 | check := columnChecker(s.TableName()) 100 | if err := check(field); err != nil { 101 | s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) 102 | return "" 103 | } 104 | return sql.Max(s.C(field)) 105 | } 106 | } 107 | 108 | // Mean applies the "mean" aggregation function on the given field of each group. 109 | func Mean(field string) AggregateFunc { 110 | return func(s *sql.Selector) string { 111 | check := columnChecker(s.TableName()) 112 | if err := check(field); err != nil { 113 | s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) 114 | return "" 115 | } 116 | return sql.Avg(s.C(field)) 117 | } 118 | } 119 | 120 | // Min applies the "min" aggregation function on the given field of each group. 121 | func Min(field string) AggregateFunc { 122 | return func(s *sql.Selector) string { 123 | check := columnChecker(s.TableName()) 124 | if err := check(field); err != nil { 125 | s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) 126 | return "" 127 | } 128 | return sql.Min(s.C(field)) 129 | } 130 | } 131 | 132 | // Sum applies the "sum" aggregation function on the given field of each group. 133 | func Sum(field string) AggregateFunc { 134 | return func(s *sql.Selector) string { 135 | check := columnChecker(s.TableName()) 136 | if err := check(field); err != nil { 137 | s.AddError(&ValidationError{Name: field, err: fmt.Errorf("ent: %w", err)}) 138 | return "" 139 | } 140 | return sql.Sum(s.C(field)) 141 | } 142 | } 143 | 144 | // ValidationError returns when validating a field fails. 145 | type ValidationError struct { 146 | Name string // Field or edge name. 147 | err error 148 | } 149 | 150 | // Error implements the error interface. 151 | func (e *ValidationError) Error() string { 152 | return e.err.Error() 153 | } 154 | 155 | // Unwrap implements the errors.Wrapper interface. 156 | func (e *ValidationError) Unwrap() error { 157 | return e.err 158 | } 159 | 160 | // IsValidationError returns a boolean indicating whether the error is a validation error. 161 | func IsValidationError(err error) bool { 162 | if err == nil { 163 | return false 164 | } 165 | var e *ValidationError 166 | return errors.As(err, &e) 167 | } 168 | 169 | // NotFoundError returns when trying to fetch a specific entity and it was not found in the database. 170 | type NotFoundError struct { 171 | label string 172 | } 173 | 174 | // Error implements the error interface. 175 | func (e *NotFoundError) Error() string { 176 | return "ent: " + e.label + " not found" 177 | } 178 | 179 | // IsNotFound returns a boolean indicating whether the error is a not found error. 180 | func IsNotFound(err error) bool { 181 | if err == nil { 182 | return false 183 | } 184 | var e *NotFoundError 185 | return errors.As(err, &e) 186 | } 187 | 188 | // MaskNotFound masks not found error. 189 | func MaskNotFound(err error) error { 190 | if IsNotFound(err) { 191 | return nil 192 | } 193 | return err 194 | } 195 | 196 | // NotSingularError returns when trying to fetch a singular entity and more then one was found in the database. 197 | type NotSingularError struct { 198 | label string 199 | } 200 | 201 | // Error implements the error interface. 202 | func (e *NotSingularError) Error() string { 203 | return "ent: " + e.label + " not singular" 204 | } 205 | 206 | // IsNotSingular returns a boolean indicating whether the error is a not singular error. 207 | func IsNotSingular(err error) bool { 208 | if err == nil { 209 | return false 210 | } 211 | var e *NotSingularError 212 | return errors.As(err, &e) 213 | } 214 | 215 | // NotLoadedError returns when trying to get a node that was not loaded by the query. 216 | type NotLoadedError struct { 217 | edge string 218 | } 219 | 220 | // Error implements the error interface. 221 | func (e *NotLoadedError) Error() string { 222 | return "ent: " + e.edge + " edge was not loaded" 223 | } 224 | 225 | // IsNotLoaded returns a boolean indicating whether the error is a not loaded error. 226 | func IsNotLoaded(err error) bool { 227 | if err == nil { 228 | return false 229 | } 230 | var e *NotLoadedError 231 | return errors.As(err, &e) 232 | } 233 | 234 | // ConstraintError returns when trying to create/update one or more entities and 235 | // one or more of their constraints failed. For example, violation of edge or 236 | // field uniqueness. 237 | type ConstraintError struct { 238 | msg string 239 | wrap error 240 | } 241 | 242 | // Error implements the error interface. 243 | func (e ConstraintError) Error() string { 244 | return "ent: constraint failed: " + e.msg 245 | } 246 | 247 | // Unwrap implements the errors.Wrapper interface. 248 | func (e *ConstraintError) Unwrap() error { 249 | return e.wrap 250 | } 251 | 252 | // IsConstraintError returns a boolean indicating whether the error is a constraint failure. 253 | func IsConstraintError(err error) bool { 254 | if err == nil { 255 | return false 256 | } 257 | var e *ConstraintError 258 | return errors.As(err, &e) 259 | } 260 | -------------------------------------------------------------------------------- /ent/enttest/enttest.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package enttest 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/fitv/min/ent" 9 | // required by schema hooks. 10 | _ "github.com/fitv/min/ent/runtime" 11 | 12 | "entgo.io/ent/dialect/sql/schema" 13 | ) 14 | 15 | type ( 16 | // TestingT is the interface that is shared between 17 | // testing.T and testing.B and used by enttest. 18 | TestingT interface { 19 | FailNow() 20 | Error(...interface{}) 21 | } 22 | 23 | // Option configures client creation. 24 | Option func(*options) 25 | 26 | options struct { 27 | opts []ent.Option 28 | migrateOpts []schema.MigrateOption 29 | } 30 | ) 31 | 32 | // WithOptions forwards options to client creation. 33 | func WithOptions(opts ...ent.Option) Option { 34 | return func(o *options) { 35 | o.opts = append(o.opts, opts...) 36 | } 37 | } 38 | 39 | // WithMigrateOptions forwards options to auto migration. 40 | func WithMigrateOptions(opts ...schema.MigrateOption) Option { 41 | return func(o *options) { 42 | o.migrateOpts = append(o.migrateOpts, opts...) 43 | } 44 | } 45 | 46 | func newOptions(opts []Option) *options { 47 | o := &options{} 48 | for _, opt := range opts { 49 | opt(o) 50 | } 51 | return o 52 | } 53 | 54 | // Open calls ent.Open and auto-run migration. 55 | func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *ent.Client { 56 | o := newOptions(opts) 57 | c, err := ent.Open(driverName, dataSourceName, o.opts...) 58 | if err != nil { 59 | t.Error(err) 60 | t.FailNow() 61 | } 62 | if err := c.Schema.Create(context.Background(), o.migrateOpts...); err != nil { 63 | t.Error(err) 64 | t.FailNow() 65 | } 66 | return c 67 | } 68 | 69 | // NewClient calls ent.NewClient and auto-run migration. 70 | func NewClient(t TestingT, opts ...Option) *ent.Client { 71 | o := newOptions(opts) 72 | c := ent.NewClient(o.opts...) 73 | if err := c.Schema.Create(context.Background(), o.migrateOpts...); err != nil { 74 | t.Error(err) 75 | t.FailNow() 76 | } 77 | return c 78 | } 79 | -------------------------------------------------------------------------------- /ent/generate.go: -------------------------------------------------------------------------------- 1 | package ent 2 | 3 | //go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/lock --template ./template ./schema 4 | -------------------------------------------------------------------------------- /ent/hook/hook.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package hook 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | 9 | "github.com/fitv/min/ent" 10 | ) 11 | 12 | // The UserFunc type is an adapter to allow the use of ordinary 13 | // function as User mutator. 14 | type UserFunc func(context.Context, *ent.UserMutation) (ent.Value, error) 15 | 16 | // Mutate calls f(ctx, m). 17 | func (f UserFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { 18 | mv, ok := m.(*ent.UserMutation) 19 | if !ok { 20 | return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UserMutation", m) 21 | } 22 | return f(ctx, mv) 23 | } 24 | 25 | // Condition is a hook condition function. 26 | type Condition func(context.Context, ent.Mutation) bool 27 | 28 | // And groups conditions with the AND operator. 29 | func And(first, second Condition, rest ...Condition) Condition { 30 | return func(ctx context.Context, m ent.Mutation) bool { 31 | if !first(ctx, m) || !second(ctx, m) { 32 | return false 33 | } 34 | for _, cond := range rest { 35 | if !cond(ctx, m) { 36 | return false 37 | } 38 | } 39 | return true 40 | } 41 | } 42 | 43 | // Or groups conditions with the OR operator. 44 | func Or(first, second Condition, rest ...Condition) Condition { 45 | return func(ctx context.Context, m ent.Mutation) bool { 46 | if first(ctx, m) || second(ctx, m) { 47 | return true 48 | } 49 | for _, cond := range rest { 50 | if cond(ctx, m) { 51 | return true 52 | } 53 | } 54 | return false 55 | } 56 | } 57 | 58 | // Not negates a given condition. 59 | func Not(cond Condition) Condition { 60 | return func(ctx context.Context, m ent.Mutation) bool { 61 | return !cond(ctx, m) 62 | } 63 | } 64 | 65 | // HasOp is a condition testing mutation operation. 66 | func HasOp(op ent.Op) Condition { 67 | return func(_ context.Context, m ent.Mutation) bool { 68 | return m.Op().Is(op) 69 | } 70 | } 71 | 72 | // HasAddedFields is a condition validating `.AddedField` on fields. 73 | func HasAddedFields(field string, fields ...string) Condition { 74 | return func(_ context.Context, m ent.Mutation) bool { 75 | if _, exists := m.AddedField(field); !exists { 76 | return false 77 | } 78 | for _, field := range fields { 79 | if _, exists := m.AddedField(field); !exists { 80 | return false 81 | } 82 | } 83 | return true 84 | } 85 | } 86 | 87 | // HasClearedFields is a condition validating `.FieldCleared` on fields. 88 | func HasClearedFields(field string, fields ...string) Condition { 89 | return func(_ context.Context, m ent.Mutation) bool { 90 | if exists := m.FieldCleared(field); !exists { 91 | return false 92 | } 93 | for _, field := range fields { 94 | if exists := m.FieldCleared(field); !exists { 95 | return false 96 | } 97 | } 98 | return true 99 | } 100 | } 101 | 102 | // HasFields is a condition validating `.Field` on fields. 103 | func HasFields(field string, fields ...string) Condition { 104 | return func(_ context.Context, m ent.Mutation) bool { 105 | if _, exists := m.Field(field); !exists { 106 | return false 107 | } 108 | for _, field := range fields { 109 | if _, exists := m.Field(field); !exists { 110 | return false 111 | } 112 | } 113 | return true 114 | } 115 | } 116 | 117 | // If executes the given hook under condition. 118 | // 119 | // hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...))) 120 | // 121 | func If(hk ent.Hook, cond Condition) ent.Hook { 122 | return func(next ent.Mutator) ent.Mutator { 123 | return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) { 124 | if cond(ctx, m) { 125 | return hk(next).Mutate(ctx, m) 126 | } 127 | return next.Mutate(ctx, m) 128 | }) 129 | } 130 | } 131 | 132 | // On executes the given hook only for the given operation. 133 | // 134 | // hook.On(Log, ent.Delete|ent.Create) 135 | // 136 | func On(hk ent.Hook, op ent.Op) ent.Hook { 137 | return If(hk, HasOp(op)) 138 | } 139 | 140 | // Unless skips the given hook only for the given operation. 141 | // 142 | // hook.Unless(Log, ent.Update|ent.UpdateOne) 143 | // 144 | func Unless(hk ent.Hook, op ent.Op) ent.Hook { 145 | return If(hk, Not(HasOp(op))) 146 | } 147 | 148 | // FixedError is a hook returning a fixed error. 149 | func FixedError(err error) ent.Hook { 150 | return func(ent.Mutator) ent.Mutator { 151 | return ent.MutateFunc(func(context.Context, ent.Mutation) (ent.Value, error) { 152 | return nil, err 153 | }) 154 | } 155 | } 156 | 157 | // Reject returns a hook that rejects all operations that match op. 158 | // 159 | // func (T) Hooks() []ent.Hook { 160 | // return []ent.Hook{ 161 | // Reject(ent.Delete|ent.Update), 162 | // } 163 | // } 164 | // 165 | func Reject(op ent.Op) ent.Hook { 166 | hk := FixedError(fmt.Errorf("%s operation is not allowed", op)) 167 | return On(hk, op) 168 | } 169 | 170 | // Chain acts as a list of hooks and is effectively immutable. 171 | // Once created, it will always hold the same set of hooks in the same order. 172 | type Chain struct { 173 | hooks []ent.Hook 174 | } 175 | 176 | // NewChain creates a new chain of hooks. 177 | func NewChain(hooks ...ent.Hook) Chain { 178 | return Chain{append([]ent.Hook(nil), hooks...)} 179 | } 180 | 181 | // Hook chains the list of hooks and returns the final hook. 182 | func (c Chain) Hook() ent.Hook { 183 | return func(mutator ent.Mutator) ent.Mutator { 184 | for i := len(c.hooks) - 1; i >= 0; i-- { 185 | mutator = c.hooks[i](mutator) 186 | } 187 | return mutator 188 | } 189 | } 190 | 191 | // Append extends a chain, adding the specified hook 192 | // as the last ones in the mutation flow. 193 | func (c Chain) Append(hooks ...ent.Hook) Chain { 194 | newHooks := make([]ent.Hook, 0, len(c.hooks)+len(hooks)) 195 | newHooks = append(newHooks, c.hooks...) 196 | newHooks = append(newHooks, hooks...) 197 | return Chain{newHooks} 198 | } 199 | 200 | // Extend extends a chain, adding the specified chain 201 | // as the last ones in the mutation flow. 202 | func (c Chain) Extend(chain Chain) Chain { 203 | return c.Append(chain.hooks...) 204 | } 205 | -------------------------------------------------------------------------------- /ent/migrate/migrate.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package migrate 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "io" 9 | 10 | "entgo.io/ent/dialect" 11 | "entgo.io/ent/dialect/sql/schema" 12 | ) 13 | 14 | var ( 15 | // WithGlobalUniqueID sets the universal ids options to the migration. 16 | // If this option is enabled, ent migration will allocate a 1<<32 range 17 | // for the ids of each entity (table). 18 | // Note that this option cannot be applied on tables that already exist. 19 | WithGlobalUniqueID = schema.WithGlobalUniqueID 20 | // WithDropColumn sets the drop column option to the migration. 21 | // If this option is enabled, ent migration will drop old columns 22 | // that were used for both fields and edges. This defaults to false. 23 | WithDropColumn = schema.WithDropColumn 24 | // WithDropIndex sets the drop index option to the migration. 25 | // If this option is enabled, ent migration will drop old indexes 26 | // that were defined in the schema. This defaults to false. 27 | // Note that unique constraints are defined using `UNIQUE INDEX`, 28 | // and therefore, it's recommended to enable this option to get more 29 | // flexibility in the schema changes. 30 | WithDropIndex = schema.WithDropIndex 31 | // WithFixture sets the foreign-key renaming option to the migration when upgrading 32 | // ent from v0.1.0 (issue-#285). Defaults to false. 33 | WithFixture = schema.WithFixture 34 | // WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true. 35 | WithForeignKeys = schema.WithForeignKeys 36 | ) 37 | 38 | // Schema is the API for creating, migrating and dropping a schema. 39 | type Schema struct { 40 | drv dialect.Driver 41 | universalID bool 42 | } 43 | 44 | // NewSchema creates a new schema client. 45 | func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} } 46 | 47 | // Create creates all schema resources. 48 | func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error { 49 | migrate, err := schema.NewMigrate(s.drv, opts...) 50 | if err != nil { 51 | return fmt.Errorf("ent/migrate: %w", err) 52 | } 53 | return migrate.Create(ctx, Tables...) 54 | } 55 | 56 | // WriteTo writes the schema changes to w instead of running them against the database. 57 | // 58 | // if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil { 59 | // log.Fatal(err) 60 | // } 61 | // 62 | func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error { 63 | drv := &schema.WriteDriver{ 64 | Writer: w, 65 | Driver: s.drv, 66 | } 67 | migrate, err := schema.NewMigrate(drv, opts...) 68 | if err != nil { 69 | return fmt.Errorf("ent/migrate: %w", err) 70 | } 71 | return migrate.Create(ctx, Tables...) 72 | } 73 | -------------------------------------------------------------------------------- /ent/migrate/schema.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package migrate 4 | 5 | import ( 6 | "entgo.io/ent/dialect/entsql" 7 | "entgo.io/ent/dialect/sql/schema" 8 | "entgo.io/ent/schema/field" 9 | ) 10 | 11 | var ( 12 | // UsersColumns holds the columns for the "users" table. 13 | UsersColumns = []*schema.Column{ 14 | {Name: "id", Type: field.TypeInt, Increment: true}, 15 | {Name: "created_at", Type: field.TypeTime}, 16 | {Name: "updated_at", Type: field.TypeTime}, 17 | {Name: "username", Type: field.TypeString, Unique: true}, 18 | {Name: "password", Type: field.TypeString}, 19 | } 20 | // UsersTable holds the schema information for the "users" table. 21 | UsersTable = &schema.Table{ 22 | Name: "users", 23 | Columns: UsersColumns, 24 | PrimaryKey: []*schema.Column{UsersColumns[0]}, 25 | } 26 | // Tables holds all the tables in the schema. 27 | Tables = []*schema.Table{ 28 | UsersTable, 29 | } 30 | ) 31 | 32 | func init() { 33 | UsersTable.Annotation = &entsql.Annotation{ 34 | Charset: "utf8mb4", 35 | Collation: "utf8mb4_unicode_ci", 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ent/mutation.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "sync" 9 | "time" 10 | 11 | "github.com/fitv/min/ent/predicate" 12 | "github.com/fitv/min/ent/user" 13 | 14 | "entgo.io/ent" 15 | ) 16 | 17 | const ( 18 | // Operation types. 19 | OpCreate = ent.OpCreate 20 | OpDelete = ent.OpDelete 21 | OpDeleteOne = ent.OpDeleteOne 22 | OpUpdate = ent.OpUpdate 23 | OpUpdateOne = ent.OpUpdateOne 24 | 25 | // Node types. 26 | TypeUser = "User" 27 | ) 28 | 29 | // UserMutation represents an operation that mutates the User nodes in the graph. 30 | type UserMutation struct { 31 | config 32 | op Op 33 | typ string 34 | id *int 35 | created_at *time.Time 36 | updated_at *time.Time 37 | username *string 38 | password *string 39 | clearedFields map[string]struct{} 40 | done bool 41 | oldValue func(context.Context) (*User, error) 42 | predicates []predicate.User 43 | } 44 | 45 | var _ ent.Mutation = (*UserMutation)(nil) 46 | 47 | // userOption allows management of the mutation configuration using functional options. 48 | type userOption func(*UserMutation) 49 | 50 | // newUserMutation creates new mutation for the User entity. 51 | func newUserMutation(c config, op Op, opts ...userOption) *UserMutation { 52 | m := &UserMutation{ 53 | config: c, 54 | op: op, 55 | typ: TypeUser, 56 | clearedFields: make(map[string]struct{}), 57 | } 58 | for _, opt := range opts { 59 | opt(m) 60 | } 61 | return m 62 | } 63 | 64 | // withUserID sets the ID field of the mutation. 65 | func withUserID(id int) userOption { 66 | return func(m *UserMutation) { 67 | var ( 68 | err error 69 | once sync.Once 70 | value *User 71 | ) 72 | m.oldValue = func(ctx context.Context) (*User, error) { 73 | once.Do(func() { 74 | if m.done { 75 | err = fmt.Errorf("querying old values post mutation is not allowed") 76 | } else { 77 | value, err = m.Client().User.Get(ctx, id) 78 | } 79 | }) 80 | return value, err 81 | } 82 | m.id = &id 83 | } 84 | } 85 | 86 | // withUser sets the old User of the mutation. 87 | func withUser(node *User) userOption { 88 | return func(m *UserMutation) { 89 | m.oldValue = func(context.Context) (*User, error) { 90 | return node, nil 91 | } 92 | m.id = &node.ID 93 | } 94 | } 95 | 96 | // Client returns a new `ent.Client` from the mutation. If the mutation was 97 | // executed in a transaction (ent.Tx), a transactional client is returned. 98 | func (m UserMutation) Client() *Client { 99 | client := &Client{config: m.config} 100 | client.init() 101 | return client 102 | } 103 | 104 | // Tx returns an `ent.Tx` for mutations that were executed in transactions; 105 | // it returns an error otherwise. 106 | func (m UserMutation) Tx() (*Tx, error) { 107 | if _, ok := m.driver.(*txDriver); !ok { 108 | return nil, fmt.Errorf("ent: mutation is not running in a transaction") 109 | } 110 | tx := &Tx{config: m.config} 111 | tx.init() 112 | return tx, nil 113 | } 114 | 115 | // ID returns the ID value in the mutation. Note that the ID is only available 116 | // if it was provided to the builder or after it was returned from the database. 117 | func (m *UserMutation) ID() (id int, exists bool) { 118 | if m.id == nil { 119 | return 120 | } 121 | return *m.id, true 122 | } 123 | 124 | // SetCreatedAt sets the "created_at" field. 125 | func (m *UserMutation) SetCreatedAt(t time.Time) { 126 | m.created_at = &t 127 | } 128 | 129 | // CreatedAt returns the value of the "created_at" field in the mutation. 130 | func (m *UserMutation) CreatedAt() (r time.Time, exists bool) { 131 | v := m.created_at 132 | if v == nil { 133 | return 134 | } 135 | return *v, true 136 | } 137 | 138 | // OldCreatedAt returns the old "created_at" field's value of the User entity. 139 | // If the User object wasn't provided to the builder, the object is fetched from the database. 140 | // An error is returned if the mutation operation is not UpdateOne, or the database query fails. 141 | func (m *UserMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { 142 | if !m.op.Is(OpUpdateOne) { 143 | return v, fmt.Errorf("OldCreatedAt is only allowed on UpdateOne operations") 144 | } 145 | if m.id == nil || m.oldValue == nil { 146 | return v, fmt.Errorf("OldCreatedAt requires an ID field in the mutation") 147 | } 148 | oldValue, err := m.oldValue(ctx) 149 | if err != nil { 150 | return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) 151 | } 152 | return oldValue.CreatedAt, nil 153 | } 154 | 155 | // ResetCreatedAt resets all changes to the "created_at" field. 156 | func (m *UserMutation) ResetCreatedAt() { 157 | m.created_at = nil 158 | } 159 | 160 | // SetUpdatedAt sets the "updated_at" field. 161 | func (m *UserMutation) SetUpdatedAt(t time.Time) { 162 | m.updated_at = &t 163 | } 164 | 165 | // UpdatedAt returns the value of the "updated_at" field in the mutation. 166 | func (m *UserMutation) UpdatedAt() (r time.Time, exists bool) { 167 | v := m.updated_at 168 | if v == nil { 169 | return 170 | } 171 | return *v, true 172 | } 173 | 174 | // OldUpdatedAt returns the old "updated_at" field's value of the User entity. 175 | // If the User object wasn't provided to the builder, the object is fetched from the database. 176 | // An error is returned if the mutation operation is not UpdateOne, or the database query fails. 177 | func (m *UserMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { 178 | if !m.op.Is(OpUpdateOne) { 179 | return v, fmt.Errorf("OldUpdatedAt is only allowed on UpdateOne operations") 180 | } 181 | if m.id == nil || m.oldValue == nil { 182 | return v, fmt.Errorf("OldUpdatedAt requires an ID field in the mutation") 183 | } 184 | oldValue, err := m.oldValue(ctx) 185 | if err != nil { 186 | return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) 187 | } 188 | return oldValue.UpdatedAt, nil 189 | } 190 | 191 | // ResetUpdatedAt resets all changes to the "updated_at" field. 192 | func (m *UserMutation) ResetUpdatedAt() { 193 | m.updated_at = nil 194 | } 195 | 196 | // SetUsername sets the "username" field. 197 | func (m *UserMutation) SetUsername(s string) { 198 | m.username = &s 199 | } 200 | 201 | // Username returns the value of the "username" field in the mutation. 202 | func (m *UserMutation) Username() (r string, exists bool) { 203 | v := m.username 204 | if v == nil { 205 | return 206 | } 207 | return *v, true 208 | } 209 | 210 | // OldUsername returns the old "username" field's value of the User entity. 211 | // If the User object wasn't provided to the builder, the object is fetched from the database. 212 | // An error is returned if the mutation operation is not UpdateOne, or the database query fails. 213 | func (m *UserMutation) OldUsername(ctx context.Context) (v string, err error) { 214 | if !m.op.Is(OpUpdateOne) { 215 | return v, fmt.Errorf("OldUsername is only allowed on UpdateOne operations") 216 | } 217 | if m.id == nil || m.oldValue == nil { 218 | return v, fmt.Errorf("OldUsername requires an ID field in the mutation") 219 | } 220 | oldValue, err := m.oldValue(ctx) 221 | if err != nil { 222 | return v, fmt.Errorf("querying old value for OldUsername: %w", err) 223 | } 224 | return oldValue.Username, nil 225 | } 226 | 227 | // ResetUsername resets all changes to the "username" field. 228 | func (m *UserMutation) ResetUsername() { 229 | m.username = nil 230 | } 231 | 232 | // SetPassword sets the "password" field. 233 | func (m *UserMutation) SetPassword(s string) { 234 | m.password = &s 235 | } 236 | 237 | // Password returns the value of the "password" field in the mutation. 238 | func (m *UserMutation) Password() (r string, exists bool) { 239 | v := m.password 240 | if v == nil { 241 | return 242 | } 243 | return *v, true 244 | } 245 | 246 | // OldPassword returns the old "password" field's value of the User entity. 247 | // If the User object wasn't provided to the builder, the object is fetched from the database. 248 | // An error is returned if the mutation operation is not UpdateOne, or the database query fails. 249 | func (m *UserMutation) OldPassword(ctx context.Context) (v string, err error) { 250 | if !m.op.Is(OpUpdateOne) { 251 | return v, fmt.Errorf("OldPassword is only allowed on UpdateOne operations") 252 | } 253 | if m.id == nil || m.oldValue == nil { 254 | return v, fmt.Errorf("OldPassword requires an ID field in the mutation") 255 | } 256 | oldValue, err := m.oldValue(ctx) 257 | if err != nil { 258 | return v, fmt.Errorf("querying old value for OldPassword: %w", err) 259 | } 260 | return oldValue.Password, nil 261 | } 262 | 263 | // ResetPassword resets all changes to the "password" field. 264 | func (m *UserMutation) ResetPassword() { 265 | m.password = nil 266 | } 267 | 268 | // Where appends a list predicates to the UserMutation builder. 269 | func (m *UserMutation) Where(ps ...predicate.User) { 270 | m.predicates = append(m.predicates, ps...) 271 | } 272 | 273 | // Op returns the operation name. 274 | func (m *UserMutation) Op() Op { 275 | return m.op 276 | } 277 | 278 | // Type returns the node type of this mutation (User). 279 | func (m *UserMutation) Type() string { 280 | return m.typ 281 | } 282 | 283 | // Fields returns all fields that were changed during this mutation. Note that in 284 | // order to get all numeric fields that were incremented/decremented, call 285 | // AddedFields(). 286 | func (m *UserMutation) Fields() []string { 287 | fields := make([]string, 0, 4) 288 | if m.created_at != nil { 289 | fields = append(fields, user.FieldCreatedAt) 290 | } 291 | if m.updated_at != nil { 292 | fields = append(fields, user.FieldUpdatedAt) 293 | } 294 | if m.username != nil { 295 | fields = append(fields, user.FieldUsername) 296 | } 297 | if m.password != nil { 298 | fields = append(fields, user.FieldPassword) 299 | } 300 | return fields 301 | } 302 | 303 | // Field returns the value of a field with the given name. The second boolean 304 | // return value indicates that this field was not set, or was not defined in the 305 | // schema. 306 | func (m *UserMutation) Field(name string) (ent.Value, bool) { 307 | switch name { 308 | case user.FieldCreatedAt: 309 | return m.CreatedAt() 310 | case user.FieldUpdatedAt: 311 | return m.UpdatedAt() 312 | case user.FieldUsername: 313 | return m.Username() 314 | case user.FieldPassword: 315 | return m.Password() 316 | } 317 | return nil, false 318 | } 319 | 320 | // OldField returns the old value of the field from the database. An error is 321 | // returned if the mutation operation is not UpdateOne, or the query to the 322 | // database failed. 323 | func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, error) { 324 | switch name { 325 | case user.FieldCreatedAt: 326 | return m.OldCreatedAt(ctx) 327 | case user.FieldUpdatedAt: 328 | return m.OldUpdatedAt(ctx) 329 | case user.FieldUsername: 330 | return m.OldUsername(ctx) 331 | case user.FieldPassword: 332 | return m.OldPassword(ctx) 333 | } 334 | return nil, fmt.Errorf("unknown User field %s", name) 335 | } 336 | 337 | // SetField sets the value of a field with the given name. It returns an error if 338 | // the field is not defined in the schema, or if the type mismatched the field 339 | // type. 340 | func (m *UserMutation) SetField(name string, value ent.Value) error { 341 | switch name { 342 | case user.FieldCreatedAt: 343 | v, ok := value.(time.Time) 344 | if !ok { 345 | return fmt.Errorf("unexpected type %T for field %s", value, name) 346 | } 347 | m.SetCreatedAt(v) 348 | return nil 349 | case user.FieldUpdatedAt: 350 | v, ok := value.(time.Time) 351 | if !ok { 352 | return fmt.Errorf("unexpected type %T for field %s", value, name) 353 | } 354 | m.SetUpdatedAt(v) 355 | return nil 356 | case user.FieldUsername: 357 | v, ok := value.(string) 358 | if !ok { 359 | return fmt.Errorf("unexpected type %T for field %s", value, name) 360 | } 361 | m.SetUsername(v) 362 | return nil 363 | case user.FieldPassword: 364 | v, ok := value.(string) 365 | if !ok { 366 | return fmt.Errorf("unexpected type %T for field %s", value, name) 367 | } 368 | m.SetPassword(v) 369 | return nil 370 | } 371 | return fmt.Errorf("unknown User field %s", name) 372 | } 373 | 374 | // AddedFields returns all numeric fields that were incremented/decremented during 375 | // this mutation. 376 | func (m *UserMutation) AddedFields() []string { 377 | return nil 378 | } 379 | 380 | // AddedField returns the numeric value that was incremented/decremented on a field 381 | // with the given name. The second boolean return value indicates that this field 382 | // was not set, or was not defined in the schema. 383 | func (m *UserMutation) AddedField(name string) (ent.Value, bool) { 384 | return nil, false 385 | } 386 | 387 | // AddField adds the value to the field with the given name. It returns an error if 388 | // the field is not defined in the schema, or if the type mismatched the field 389 | // type. 390 | func (m *UserMutation) AddField(name string, value ent.Value) error { 391 | switch name { 392 | } 393 | return fmt.Errorf("unknown User numeric field %s", name) 394 | } 395 | 396 | // ClearedFields returns all nullable fields that were cleared during this 397 | // mutation. 398 | func (m *UserMutation) ClearedFields() []string { 399 | return nil 400 | } 401 | 402 | // FieldCleared returns a boolean indicating if a field with the given name was 403 | // cleared in this mutation. 404 | func (m *UserMutation) FieldCleared(name string) bool { 405 | _, ok := m.clearedFields[name] 406 | return ok 407 | } 408 | 409 | // ClearField clears the value of the field with the given name. It returns an 410 | // error if the field is not defined in the schema. 411 | func (m *UserMutation) ClearField(name string) error { 412 | return fmt.Errorf("unknown User nullable field %s", name) 413 | } 414 | 415 | // ResetField resets all changes in the mutation for the field with the given name. 416 | // It returns an error if the field is not defined in the schema. 417 | func (m *UserMutation) ResetField(name string) error { 418 | switch name { 419 | case user.FieldCreatedAt: 420 | m.ResetCreatedAt() 421 | return nil 422 | case user.FieldUpdatedAt: 423 | m.ResetUpdatedAt() 424 | return nil 425 | case user.FieldUsername: 426 | m.ResetUsername() 427 | return nil 428 | case user.FieldPassword: 429 | m.ResetPassword() 430 | return nil 431 | } 432 | return fmt.Errorf("unknown User field %s", name) 433 | } 434 | 435 | // AddedEdges returns all edge names that were set/added in this mutation. 436 | func (m *UserMutation) AddedEdges() []string { 437 | edges := make([]string, 0, 0) 438 | return edges 439 | } 440 | 441 | // AddedIDs returns all IDs (to other nodes) that were added for the given edge 442 | // name in this mutation. 443 | func (m *UserMutation) AddedIDs(name string) []ent.Value { 444 | return nil 445 | } 446 | 447 | // RemovedEdges returns all edge names that were removed in this mutation. 448 | func (m *UserMutation) RemovedEdges() []string { 449 | edges := make([]string, 0, 0) 450 | return edges 451 | } 452 | 453 | // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with 454 | // the given name in this mutation. 455 | func (m *UserMutation) RemovedIDs(name string) []ent.Value { 456 | return nil 457 | } 458 | 459 | // ClearedEdges returns all edge names that were cleared in this mutation. 460 | func (m *UserMutation) ClearedEdges() []string { 461 | edges := make([]string, 0, 0) 462 | return edges 463 | } 464 | 465 | // EdgeCleared returns a boolean which indicates if the edge with the given name 466 | // was cleared in this mutation. 467 | func (m *UserMutation) EdgeCleared(name string) bool { 468 | return false 469 | } 470 | 471 | // ClearEdge clears the value of the edge with the given name. It returns an error 472 | // if that edge is not defined in the schema. 473 | func (m *UserMutation) ClearEdge(name string) error { 474 | return fmt.Errorf("unknown User unique edge %s", name) 475 | } 476 | 477 | // ResetEdge resets all changes to the edge with the given name in this mutation. 478 | // It returns an error if the edge is not defined in the schema. 479 | func (m *UserMutation) ResetEdge(name string) error { 480 | return fmt.Errorf("unknown User edge %s", name) 481 | } 482 | -------------------------------------------------------------------------------- /ent/paginator.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "math" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/spf13/cast" 11 | ) 12 | 13 | type Paginator struct { 14 | CurrentPage int `json:"current_page"` 15 | PerPage int `json:"per_page"` 16 | LastPage int `json:"last_page"` 17 | Total int `json:"total"` 18 | Data interface{} `json:"data"` 19 | } 20 | 21 | func (query *UserQuery) Paginate(ctx context.Context, c *gin.Context) (*Paginator, error) { 22 | page := cast.ToInt(c.Query("page")) 23 | perPage := cast.ToInt(c.Query("per_page")) 24 | if page < 1 { 25 | page = 1 26 | } 27 | if perPage < 1 { 28 | perPage = 15 29 | } 30 | offset := perPage * (page - 1) 31 | 32 | total, err := query.Clone().Count(ctx) 33 | if err != nil { 34 | return nil, err 35 | } 36 | var data interface{} 37 | data, err = query.Offset(offset).Limit(perPage).All(ctx) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | return &Paginator{ 43 | Data: data, 44 | Total: total, 45 | PerPage: perPage, 46 | CurrentPage: page, 47 | LastPage: int(math.Ceil(float64(total) / float64(perPage))), 48 | }, nil 49 | } 50 | -------------------------------------------------------------------------------- /ent/predicate/predicate.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package predicate 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | ) 8 | 9 | // User is the predicate function for user builders. 10 | type User func(*sql.Selector) 11 | -------------------------------------------------------------------------------- /ent/runtime.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | // The schema-stitching logic is generated in github.com/fitv/min/ent/runtime/runtime.go 6 | -------------------------------------------------------------------------------- /ent/runtime/runtime.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package runtime 4 | 5 | import ( 6 | "time" 7 | 8 | "github.com/fitv/min/ent/schema" 9 | "github.com/fitv/min/ent/user" 10 | ) 11 | 12 | // The init function reads all schema descriptors with runtime code 13 | // (default values, validators, hooks and policies) and stitches it 14 | // to their package variables. 15 | func init() { 16 | userMixin := schema.User{}.Mixin() 17 | userHooks := schema.User{}.Hooks() 18 | user.Hooks[0] = userHooks[0] 19 | userMixinFields0 := userMixin[0].Fields() 20 | _ = userMixinFields0 21 | userFields := schema.User{}.Fields() 22 | _ = userFields 23 | // userDescCreatedAt is the schema descriptor for created_at field. 24 | userDescCreatedAt := userMixinFields0[0].Descriptor() 25 | // user.DefaultCreatedAt holds the default value on creation for the created_at field. 26 | user.DefaultCreatedAt = userDescCreatedAt.Default.(func() time.Time) 27 | // userDescUpdatedAt is the schema descriptor for updated_at field. 28 | userDescUpdatedAt := userMixinFields0[1].Descriptor() 29 | // user.DefaultUpdatedAt holds the default value on creation for the updated_at field. 30 | user.DefaultUpdatedAt = userDescUpdatedAt.Default.(func() time.Time) 31 | // user.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. 32 | user.UpdateDefaultUpdatedAt = userDescUpdatedAt.UpdateDefault.(func() time.Time) 33 | } 34 | 35 | const ( 36 | Version = "v0.9.1" // Version of ent codegen. 37 | Sum = "h1:IG8andyeD79GG24U8Q+1Y45hQXj6gY5evSBcva5gtBk=" // Sum of ent codegen. 38 | ) 39 | -------------------------------------------------------------------------------- /ent/schema/schema.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "time" 5 | 6 | "entgo.io/ent" 7 | "entgo.io/ent/dialect/entsql" 8 | "entgo.io/ent/schema" 9 | "entgo.io/ent/schema/field" 10 | "entgo.io/ent/schema/mixin" 11 | ) 12 | 13 | type TimeMixin struct { 14 | mixin.Schema 15 | } 16 | 17 | func (TimeMixin) Fields() []ent.Field { 18 | return []ent.Field{ 19 | field.Time("created_at"). 20 | Immutable(). 21 | Default(time.Now), 22 | field.Time("updated_at"). 23 | Default(time.Now). 24 | UpdateDefault(time.Now), 25 | } 26 | } 27 | 28 | type CharsetMixin struct { 29 | mixin.Schema 30 | } 31 | 32 | func (CharsetMixin) Annotations() []schema.Annotation { 33 | return []schema.Annotation{ 34 | entsql.Annotation{ 35 | Charset: "utf8mb4", 36 | Collation: "utf8mb4_unicode_ci", 37 | }, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ent/schema/user.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "entgo.io/ent" 8 | "entgo.io/ent/schema/field" 9 | gen "github.com/fitv/min/ent" 10 | "github.com/fitv/min/ent/hook" 11 | "github.com/fitv/min/util/hash" 12 | ) 13 | 14 | // User holds the schema definition for the User entity. 15 | type User struct { 16 | ent.Schema 17 | } 18 | 19 | // Mixin of the User. 20 | func (User) Mixin() []ent.Mixin { 21 | return []ent.Mixin{ 22 | TimeMixin{}, 23 | CharsetMixin{}, 24 | } 25 | } 26 | 27 | // Fields of the User. 28 | func (User) Fields() []ent.Field { 29 | return []ent.Field{ 30 | field.String("username").Unique(), 31 | field.String("password").Sensitive(), 32 | } 33 | } 34 | 35 | // Hooks of the User. 36 | func (User) Hooks() []ent.Hook { 37 | return []ent.Hook{ 38 | hook.On( 39 | func(next ent.Mutator) ent.Mutator { 40 | return hook.UserFunc(func(ctx context.Context, m *gen.UserMutation) (ent.Value, error) { 41 | if password, ok := m.Password(); ok && len(password) <= 20 { 42 | hashPassword, err := hash.Make(password) 43 | if err != nil { 44 | return nil, fmt.Errorf("failed to hash password: %w", err) 45 | } 46 | m.SetPassword(string(hashPassword)) 47 | } 48 | return next.Mutate(ctx, m) 49 | }) 50 | }, 51 | ent.OpCreate|ent.OpUpdate|ent.OpUpdateOne, 52 | ), 53 | } 54 | } 55 | 56 | // Edges of the User. 57 | func (User) Edges() []ent.Edge { 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /ent/template/paginator.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "paginator" }} 2 | 3 | {{ template "header" $ }} 4 | 5 | {{ $paginator := "Paginator" -}} 6 | 7 | type {{ $paginator }} struct { 8 | CurrentPage int `json:"current_page"` 9 | PerPage int `json:"per_page"` 10 | LastPage int `json:"last_page"` 11 | Total int `json:"total"` 12 | Data interface{} `json:"data"` 13 | } 14 | 15 | {{ range $node := $.Nodes -}} 16 | 17 | {{ $name := $node.Name }} 18 | 19 | {{ $q := "query" -}} 20 | {{ $query := print $node.QueryName -}} 21 | func ({{ $q }} *{{ $query }}) Paginate(ctx context.Context, c *gin.Context) (*{{ $paginator }}, error) { 22 | page := cast.ToInt(c.Query("page")) 23 | perPage := cast.ToInt(c.Query("per_page")) 24 | if page < 1 { 25 | page = 1 26 | } 27 | if perPage < 1 { 28 | perPage = 15 29 | } 30 | offset := perPage * (page - 1) 31 | 32 | total, err := {{ $q }}.Clone().Count(ctx) 33 | if err != nil { 34 | return nil, err 35 | } 36 | var data interface{} 37 | data, err = {{ $q }}.Offset(offset).Limit(perPage).All(ctx) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | return &{{ $paginator }}{ 43 | Data: data, 44 | Total: total, 45 | PerPage: perPage, 46 | CurrentPage: page, 47 | LastPage: int(math.Ceil(float64(total) / float64(perPage))), 48 | }, nil 49 | } 50 | 51 | {{- end }} 52 | 53 | {{ end }} 54 | -------------------------------------------------------------------------------- /ent/tx.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "sync" 8 | 9 | "entgo.io/ent/dialect" 10 | ) 11 | 12 | // Tx is a transactional client that is created by calling Client.Tx(). 13 | type Tx struct { 14 | config 15 | // User is the client for interacting with the User builders. 16 | User *UserClient 17 | 18 | // lazily loaded. 19 | client *Client 20 | clientOnce sync.Once 21 | 22 | // completion callbacks. 23 | mu sync.Mutex 24 | onCommit []CommitHook 25 | onRollback []RollbackHook 26 | 27 | // ctx lives for the life of the transaction. It is 28 | // the same context used by the underlying connection. 29 | ctx context.Context 30 | } 31 | 32 | type ( 33 | // Committer is the interface that wraps the Committer method. 34 | Committer interface { 35 | Commit(context.Context, *Tx) error 36 | } 37 | 38 | // The CommitFunc type is an adapter to allow the use of ordinary 39 | // function as a Committer. If f is a function with the appropriate 40 | // signature, CommitFunc(f) is a Committer that calls f. 41 | CommitFunc func(context.Context, *Tx) error 42 | 43 | // CommitHook defines the "commit middleware". A function that gets a Committer 44 | // and returns a Committer. For example: 45 | // 46 | // hook := func(next ent.Committer) ent.Committer { 47 | // return ent.CommitFunc(func(context.Context, tx *ent.Tx) error { 48 | // // Do some stuff before. 49 | // if err := next.Commit(ctx, tx); err != nil { 50 | // return err 51 | // } 52 | // // Do some stuff after. 53 | // return nil 54 | // }) 55 | // } 56 | // 57 | CommitHook func(Committer) Committer 58 | ) 59 | 60 | // Commit calls f(ctx, m). 61 | func (f CommitFunc) Commit(ctx context.Context, tx *Tx) error { 62 | return f(ctx, tx) 63 | } 64 | 65 | // Commit commits the transaction. 66 | func (tx *Tx) Commit() error { 67 | txDriver := tx.config.driver.(*txDriver) 68 | var fn Committer = CommitFunc(func(context.Context, *Tx) error { 69 | return txDriver.tx.Commit() 70 | }) 71 | tx.mu.Lock() 72 | hooks := append([]CommitHook(nil), tx.onCommit...) 73 | tx.mu.Unlock() 74 | for i := len(hooks) - 1; i >= 0; i-- { 75 | fn = hooks[i](fn) 76 | } 77 | return fn.Commit(tx.ctx, tx) 78 | } 79 | 80 | // OnCommit adds a hook to call on commit. 81 | func (tx *Tx) OnCommit(f CommitHook) { 82 | tx.mu.Lock() 83 | defer tx.mu.Unlock() 84 | tx.onCommit = append(tx.onCommit, f) 85 | } 86 | 87 | type ( 88 | // Rollbacker is the interface that wraps the Rollbacker method. 89 | Rollbacker interface { 90 | Rollback(context.Context, *Tx) error 91 | } 92 | 93 | // The RollbackFunc type is an adapter to allow the use of ordinary 94 | // function as a Rollbacker. If f is a function with the appropriate 95 | // signature, RollbackFunc(f) is a Rollbacker that calls f. 96 | RollbackFunc func(context.Context, *Tx) error 97 | 98 | // RollbackHook defines the "rollback middleware". A function that gets a Rollbacker 99 | // and returns a Rollbacker. For example: 100 | // 101 | // hook := func(next ent.Rollbacker) ent.Rollbacker { 102 | // return ent.RollbackFunc(func(context.Context, tx *ent.Tx) error { 103 | // // Do some stuff before. 104 | // if err := next.Rollback(ctx, tx); err != nil { 105 | // return err 106 | // } 107 | // // Do some stuff after. 108 | // return nil 109 | // }) 110 | // } 111 | // 112 | RollbackHook func(Rollbacker) Rollbacker 113 | ) 114 | 115 | // Rollback calls f(ctx, m). 116 | func (f RollbackFunc) Rollback(ctx context.Context, tx *Tx) error { 117 | return f(ctx, tx) 118 | } 119 | 120 | // Rollback rollbacks the transaction. 121 | func (tx *Tx) Rollback() error { 122 | txDriver := tx.config.driver.(*txDriver) 123 | var fn Rollbacker = RollbackFunc(func(context.Context, *Tx) error { 124 | return txDriver.tx.Rollback() 125 | }) 126 | tx.mu.Lock() 127 | hooks := append([]RollbackHook(nil), tx.onRollback...) 128 | tx.mu.Unlock() 129 | for i := len(hooks) - 1; i >= 0; i-- { 130 | fn = hooks[i](fn) 131 | } 132 | return fn.Rollback(tx.ctx, tx) 133 | } 134 | 135 | // OnRollback adds a hook to call on rollback. 136 | func (tx *Tx) OnRollback(f RollbackHook) { 137 | tx.mu.Lock() 138 | defer tx.mu.Unlock() 139 | tx.onRollback = append(tx.onRollback, f) 140 | } 141 | 142 | // Client returns a Client that binds to current transaction. 143 | func (tx *Tx) Client() *Client { 144 | tx.clientOnce.Do(func() { 145 | tx.client = &Client{config: tx.config} 146 | tx.client.init() 147 | }) 148 | return tx.client 149 | } 150 | 151 | func (tx *Tx) init() { 152 | tx.User = NewUserClient(tx.config) 153 | } 154 | 155 | // txDriver wraps the given dialect.Tx with a nop dialect.Driver implementation. 156 | // The idea is to support transactions without adding any extra code to the builders. 157 | // When a builder calls to driver.Tx(), it gets the same dialect.Tx instance. 158 | // Commit and Rollback are nop for the internal builders and the user must call one 159 | // of them in order to commit or rollback the transaction. 160 | // 161 | // If a closed transaction is embedded in one of the generated entities, and the entity 162 | // applies a query, for example: User.QueryXXX(), the query will be executed 163 | // through the driver which created this transaction. 164 | // 165 | // Note that txDriver is not goroutine safe. 166 | type txDriver struct { 167 | // the driver we started the transaction from. 168 | drv dialect.Driver 169 | // tx is the underlying transaction. 170 | tx dialect.Tx 171 | } 172 | 173 | // newTx creates a new transactional driver. 174 | func newTx(ctx context.Context, drv dialect.Driver) (*txDriver, error) { 175 | tx, err := drv.Tx(ctx) 176 | if err != nil { 177 | return nil, err 178 | } 179 | return &txDriver{tx: tx, drv: drv}, nil 180 | } 181 | 182 | // Tx returns the transaction wrapper (txDriver) to avoid Commit or Rollback calls 183 | // from the internal builders. Should be called only by the internal builders. 184 | func (tx *txDriver) Tx(context.Context) (dialect.Tx, error) { return tx, nil } 185 | 186 | // Dialect returns the dialect of the driver we started the transaction from. 187 | func (tx *txDriver) Dialect() string { return tx.drv.Dialect() } 188 | 189 | // Close is a nop close. 190 | func (*txDriver) Close() error { return nil } 191 | 192 | // Commit is a nop commit for the internal builders. 193 | // User must call `Tx.Commit` in order to commit the transaction. 194 | func (*txDriver) Commit() error { return nil } 195 | 196 | // Rollback is a nop rollback for the internal builders. 197 | // User must call `Tx.Rollback` in order to rollback the transaction. 198 | func (*txDriver) Rollback() error { return nil } 199 | 200 | // Exec calls tx.Exec. 201 | func (tx *txDriver) Exec(ctx context.Context, query string, args, v interface{}) error { 202 | return tx.tx.Exec(ctx, query, args, v) 203 | } 204 | 205 | // Query calls tx.Query. 206 | func (tx *txDriver) Query(ctx context.Context, query string, args, v interface{}) error { 207 | return tx.tx.Query(ctx, query, args, v) 208 | } 209 | 210 | var _ dialect.Driver = (*txDriver)(nil) 211 | -------------------------------------------------------------------------------- /ent/user.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | "time" 9 | 10 | "entgo.io/ent/dialect/sql" 11 | "github.com/fitv/min/ent/user" 12 | ) 13 | 14 | // User is the model entity for the User schema. 15 | type User struct { 16 | config `json:"-"` 17 | // ID of the ent. 18 | ID int `json:"id,omitempty"` 19 | // CreatedAt holds the value of the "created_at" field. 20 | CreatedAt time.Time `json:"created_at,omitempty"` 21 | // UpdatedAt holds the value of the "updated_at" field. 22 | UpdatedAt time.Time `json:"updated_at,omitempty"` 23 | // Username holds the value of the "username" field. 24 | Username string `json:"username,omitempty"` 25 | // Password holds the value of the "password" field. 26 | Password string `json:"-"` 27 | } 28 | 29 | // scanValues returns the types for scanning values from sql.Rows. 30 | func (*User) scanValues(columns []string) ([]interface{}, error) { 31 | values := make([]interface{}, len(columns)) 32 | for i := range columns { 33 | switch columns[i] { 34 | case user.FieldID: 35 | values[i] = new(sql.NullInt64) 36 | case user.FieldUsername, user.FieldPassword: 37 | values[i] = new(sql.NullString) 38 | case user.FieldCreatedAt, user.FieldUpdatedAt: 39 | values[i] = new(sql.NullTime) 40 | default: 41 | return nil, fmt.Errorf("unexpected column %q for type User", columns[i]) 42 | } 43 | } 44 | return values, nil 45 | } 46 | 47 | // assignValues assigns the values that were returned from sql.Rows (after scanning) 48 | // to the User fields. 49 | func (u *User) assignValues(columns []string, values []interface{}) error { 50 | if m, n := len(values), len(columns); m < n { 51 | return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) 52 | } 53 | for i := range columns { 54 | switch columns[i] { 55 | case user.FieldID: 56 | value, ok := values[i].(*sql.NullInt64) 57 | if !ok { 58 | return fmt.Errorf("unexpected type %T for field id", value) 59 | } 60 | u.ID = int(value.Int64) 61 | case user.FieldCreatedAt: 62 | if value, ok := values[i].(*sql.NullTime); !ok { 63 | return fmt.Errorf("unexpected type %T for field created_at", values[i]) 64 | } else if value.Valid { 65 | u.CreatedAt = value.Time 66 | } 67 | case user.FieldUpdatedAt: 68 | if value, ok := values[i].(*sql.NullTime); !ok { 69 | return fmt.Errorf("unexpected type %T for field updated_at", values[i]) 70 | } else if value.Valid { 71 | u.UpdatedAt = value.Time 72 | } 73 | case user.FieldUsername: 74 | if value, ok := values[i].(*sql.NullString); !ok { 75 | return fmt.Errorf("unexpected type %T for field username", values[i]) 76 | } else if value.Valid { 77 | u.Username = value.String 78 | } 79 | case user.FieldPassword: 80 | if value, ok := values[i].(*sql.NullString); !ok { 81 | return fmt.Errorf("unexpected type %T for field password", values[i]) 82 | } else if value.Valid { 83 | u.Password = value.String 84 | } 85 | } 86 | } 87 | return nil 88 | } 89 | 90 | // Update returns a builder for updating this User. 91 | // Note that you need to call User.Unwrap() before calling this method if this User 92 | // was returned from a transaction, and the transaction was committed or rolled back. 93 | func (u *User) Update() *UserUpdateOne { 94 | return (&UserClient{config: u.config}).UpdateOne(u) 95 | } 96 | 97 | // Unwrap unwraps the User entity that was returned from a transaction after it was closed, 98 | // so that all future queries will be executed through the driver which created the transaction. 99 | func (u *User) Unwrap() *User { 100 | tx, ok := u.config.driver.(*txDriver) 101 | if !ok { 102 | panic("ent: User is not a transactional entity") 103 | } 104 | u.config.driver = tx.drv 105 | return u 106 | } 107 | 108 | // String implements the fmt.Stringer. 109 | func (u *User) String() string { 110 | var builder strings.Builder 111 | builder.WriteString("User(") 112 | builder.WriteString(fmt.Sprintf("id=%v", u.ID)) 113 | builder.WriteString(", created_at=") 114 | builder.WriteString(u.CreatedAt.Format(time.ANSIC)) 115 | builder.WriteString(", updated_at=") 116 | builder.WriteString(u.UpdatedAt.Format(time.ANSIC)) 117 | builder.WriteString(", username=") 118 | builder.WriteString(u.Username) 119 | builder.WriteString(", password=") 120 | builder.WriteByte(')') 121 | return builder.String() 122 | } 123 | 124 | // Users is a parsable slice of User. 125 | type Users []*User 126 | 127 | func (u Users) config(cfg config) { 128 | for _i := range u { 129 | u[_i].config = cfg 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /ent/user/user.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package user 4 | 5 | import ( 6 | "time" 7 | 8 | "entgo.io/ent" 9 | ) 10 | 11 | const ( 12 | // Label holds the string label denoting the user type in the database. 13 | Label = "user" 14 | // FieldID holds the string denoting the id field in the database. 15 | FieldID = "id" 16 | // FieldCreatedAt holds the string denoting the created_at field in the database. 17 | FieldCreatedAt = "created_at" 18 | // FieldUpdatedAt holds the string denoting the updated_at field in the database. 19 | FieldUpdatedAt = "updated_at" 20 | // FieldUsername holds the string denoting the username field in the database. 21 | FieldUsername = "username" 22 | // FieldPassword holds the string denoting the password field in the database. 23 | FieldPassword = "password" 24 | // Table holds the table name of the user in the database. 25 | Table = "users" 26 | ) 27 | 28 | // Columns holds all SQL columns for user fields. 29 | var Columns = []string{ 30 | FieldID, 31 | FieldCreatedAt, 32 | FieldUpdatedAt, 33 | FieldUsername, 34 | FieldPassword, 35 | } 36 | 37 | // ValidColumn reports if the column name is valid (part of the table columns). 38 | func ValidColumn(column string) bool { 39 | for i := range Columns { 40 | if column == Columns[i] { 41 | return true 42 | } 43 | } 44 | return false 45 | } 46 | 47 | // Note that the variables below are initialized by the runtime 48 | // package on the initialization of the application. Therefore, 49 | // it should be imported in the main as follows: 50 | // 51 | // import _ "github.com/fitv/min/ent/runtime" 52 | // 53 | var ( 54 | Hooks [1]ent.Hook 55 | // DefaultCreatedAt holds the default value on creation for the "created_at" field. 56 | DefaultCreatedAt func() time.Time 57 | // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. 58 | DefaultUpdatedAt func() time.Time 59 | // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. 60 | UpdateDefaultUpdatedAt func() time.Time 61 | ) 62 | -------------------------------------------------------------------------------- /ent/user/where.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package user 4 | 5 | import ( 6 | "time" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "github.com/fitv/min/ent/predicate" 10 | ) 11 | 12 | // ID filters vertices based on their ID field. 13 | func ID(id int) predicate.User { 14 | return predicate.User(func(s *sql.Selector) { 15 | s.Where(sql.EQ(s.C(FieldID), id)) 16 | }) 17 | } 18 | 19 | // IDEQ applies the EQ predicate on the ID field. 20 | func IDEQ(id int) predicate.User { 21 | return predicate.User(func(s *sql.Selector) { 22 | s.Where(sql.EQ(s.C(FieldID), id)) 23 | }) 24 | } 25 | 26 | // IDNEQ applies the NEQ predicate on the ID field. 27 | func IDNEQ(id int) predicate.User { 28 | return predicate.User(func(s *sql.Selector) { 29 | s.Where(sql.NEQ(s.C(FieldID), id)) 30 | }) 31 | } 32 | 33 | // IDIn applies the In predicate on the ID field. 34 | func IDIn(ids ...int) predicate.User { 35 | return predicate.User(func(s *sql.Selector) { 36 | // if not arguments were provided, append the FALSE constants, 37 | // since we can't apply "IN ()". This will make this predicate falsy. 38 | if len(ids) == 0 { 39 | s.Where(sql.False()) 40 | return 41 | } 42 | v := make([]interface{}, len(ids)) 43 | for i := range v { 44 | v[i] = ids[i] 45 | } 46 | s.Where(sql.In(s.C(FieldID), v...)) 47 | }) 48 | } 49 | 50 | // IDNotIn applies the NotIn predicate on the ID field. 51 | func IDNotIn(ids ...int) predicate.User { 52 | return predicate.User(func(s *sql.Selector) { 53 | // if not arguments were provided, append the FALSE constants, 54 | // since we can't apply "IN ()". This will make this predicate falsy. 55 | if len(ids) == 0 { 56 | s.Where(sql.False()) 57 | return 58 | } 59 | v := make([]interface{}, len(ids)) 60 | for i := range v { 61 | v[i] = ids[i] 62 | } 63 | s.Where(sql.NotIn(s.C(FieldID), v...)) 64 | }) 65 | } 66 | 67 | // IDGT applies the GT predicate on the ID field. 68 | func IDGT(id int) predicate.User { 69 | return predicate.User(func(s *sql.Selector) { 70 | s.Where(sql.GT(s.C(FieldID), id)) 71 | }) 72 | } 73 | 74 | // IDGTE applies the GTE predicate on the ID field. 75 | func IDGTE(id int) predicate.User { 76 | return predicate.User(func(s *sql.Selector) { 77 | s.Where(sql.GTE(s.C(FieldID), id)) 78 | }) 79 | } 80 | 81 | // IDLT applies the LT predicate on the ID field. 82 | func IDLT(id int) predicate.User { 83 | return predicate.User(func(s *sql.Selector) { 84 | s.Where(sql.LT(s.C(FieldID), id)) 85 | }) 86 | } 87 | 88 | // IDLTE applies the LTE predicate on the ID field. 89 | func IDLTE(id int) predicate.User { 90 | return predicate.User(func(s *sql.Selector) { 91 | s.Where(sql.LTE(s.C(FieldID), id)) 92 | }) 93 | } 94 | 95 | // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. 96 | func CreatedAt(v time.Time) predicate.User { 97 | return predicate.User(func(s *sql.Selector) { 98 | s.Where(sql.EQ(s.C(FieldCreatedAt), v)) 99 | }) 100 | } 101 | 102 | // UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. 103 | func UpdatedAt(v time.Time) predicate.User { 104 | return predicate.User(func(s *sql.Selector) { 105 | s.Where(sql.EQ(s.C(FieldUpdatedAt), v)) 106 | }) 107 | } 108 | 109 | // Username applies equality check predicate on the "username" field. It's identical to UsernameEQ. 110 | func Username(v string) predicate.User { 111 | return predicate.User(func(s *sql.Selector) { 112 | s.Where(sql.EQ(s.C(FieldUsername), v)) 113 | }) 114 | } 115 | 116 | // Password applies equality check predicate on the "password" field. It's identical to PasswordEQ. 117 | func Password(v string) predicate.User { 118 | return predicate.User(func(s *sql.Selector) { 119 | s.Where(sql.EQ(s.C(FieldPassword), v)) 120 | }) 121 | } 122 | 123 | // CreatedAtEQ applies the EQ predicate on the "created_at" field. 124 | func CreatedAtEQ(v time.Time) predicate.User { 125 | return predicate.User(func(s *sql.Selector) { 126 | s.Where(sql.EQ(s.C(FieldCreatedAt), v)) 127 | }) 128 | } 129 | 130 | // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. 131 | func CreatedAtNEQ(v time.Time) predicate.User { 132 | return predicate.User(func(s *sql.Selector) { 133 | s.Where(sql.NEQ(s.C(FieldCreatedAt), v)) 134 | }) 135 | } 136 | 137 | // CreatedAtIn applies the In predicate on the "created_at" field. 138 | func CreatedAtIn(vs ...time.Time) predicate.User { 139 | v := make([]interface{}, len(vs)) 140 | for i := range v { 141 | v[i] = vs[i] 142 | } 143 | return predicate.User(func(s *sql.Selector) { 144 | // if not arguments were provided, append the FALSE constants, 145 | // since we can't apply "IN ()". This will make this predicate falsy. 146 | if len(v) == 0 { 147 | s.Where(sql.False()) 148 | return 149 | } 150 | s.Where(sql.In(s.C(FieldCreatedAt), v...)) 151 | }) 152 | } 153 | 154 | // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. 155 | func CreatedAtNotIn(vs ...time.Time) predicate.User { 156 | v := make([]interface{}, len(vs)) 157 | for i := range v { 158 | v[i] = vs[i] 159 | } 160 | return predicate.User(func(s *sql.Selector) { 161 | // if not arguments were provided, append the FALSE constants, 162 | // since we can't apply "IN ()". This will make this predicate falsy. 163 | if len(v) == 0 { 164 | s.Where(sql.False()) 165 | return 166 | } 167 | s.Where(sql.NotIn(s.C(FieldCreatedAt), v...)) 168 | }) 169 | } 170 | 171 | // CreatedAtGT applies the GT predicate on the "created_at" field. 172 | func CreatedAtGT(v time.Time) predicate.User { 173 | return predicate.User(func(s *sql.Selector) { 174 | s.Where(sql.GT(s.C(FieldCreatedAt), v)) 175 | }) 176 | } 177 | 178 | // CreatedAtGTE applies the GTE predicate on the "created_at" field. 179 | func CreatedAtGTE(v time.Time) predicate.User { 180 | return predicate.User(func(s *sql.Selector) { 181 | s.Where(sql.GTE(s.C(FieldCreatedAt), v)) 182 | }) 183 | } 184 | 185 | // CreatedAtLT applies the LT predicate on the "created_at" field. 186 | func CreatedAtLT(v time.Time) predicate.User { 187 | return predicate.User(func(s *sql.Selector) { 188 | s.Where(sql.LT(s.C(FieldCreatedAt), v)) 189 | }) 190 | } 191 | 192 | // CreatedAtLTE applies the LTE predicate on the "created_at" field. 193 | func CreatedAtLTE(v time.Time) predicate.User { 194 | return predicate.User(func(s *sql.Selector) { 195 | s.Where(sql.LTE(s.C(FieldCreatedAt), v)) 196 | }) 197 | } 198 | 199 | // UpdatedAtEQ applies the EQ predicate on the "updated_at" field. 200 | func UpdatedAtEQ(v time.Time) predicate.User { 201 | return predicate.User(func(s *sql.Selector) { 202 | s.Where(sql.EQ(s.C(FieldUpdatedAt), v)) 203 | }) 204 | } 205 | 206 | // UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. 207 | func UpdatedAtNEQ(v time.Time) predicate.User { 208 | return predicate.User(func(s *sql.Selector) { 209 | s.Where(sql.NEQ(s.C(FieldUpdatedAt), v)) 210 | }) 211 | } 212 | 213 | // UpdatedAtIn applies the In predicate on the "updated_at" field. 214 | func UpdatedAtIn(vs ...time.Time) predicate.User { 215 | v := make([]interface{}, len(vs)) 216 | for i := range v { 217 | v[i] = vs[i] 218 | } 219 | return predicate.User(func(s *sql.Selector) { 220 | // if not arguments were provided, append the FALSE constants, 221 | // since we can't apply "IN ()". This will make this predicate falsy. 222 | if len(v) == 0 { 223 | s.Where(sql.False()) 224 | return 225 | } 226 | s.Where(sql.In(s.C(FieldUpdatedAt), v...)) 227 | }) 228 | } 229 | 230 | // UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. 231 | func UpdatedAtNotIn(vs ...time.Time) predicate.User { 232 | v := make([]interface{}, len(vs)) 233 | for i := range v { 234 | v[i] = vs[i] 235 | } 236 | return predicate.User(func(s *sql.Selector) { 237 | // if not arguments were provided, append the FALSE constants, 238 | // since we can't apply "IN ()". This will make this predicate falsy. 239 | if len(v) == 0 { 240 | s.Where(sql.False()) 241 | return 242 | } 243 | s.Where(sql.NotIn(s.C(FieldUpdatedAt), v...)) 244 | }) 245 | } 246 | 247 | // UpdatedAtGT applies the GT predicate on the "updated_at" field. 248 | func UpdatedAtGT(v time.Time) predicate.User { 249 | return predicate.User(func(s *sql.Selector) { 250 | s.Where(sql.GT(s.C(FieldUpdatedAt), v)) 251 | }) 252 | } 253 | 254 | // UpdatedAtGTE applies the GTE predicate on the "updated_at" field. 255 | func UpdatedAtGTE(v time.Time) predicate.User { 256 | return predicate.User(func(s *sql.Selector) { 257 | s.Where(sql.GTE(s.C(FieldUpdatedAt), v)) 258 | }) 259 | } 260 | 261 | // UpdatedAtLT applies the LT predicate on the "updated_at" field. 262 | func UpdatedAtLT(v time.Time) predicate.User { 263 | return predicate.User(func(s *sql.Selector) { 264 | s.Where(sql.LT(s.C(FieldUpdatedAt), v)) 265 | }) 266 | } 267 | 268 | // UpdatedAtLTE applies the LTE predicate on the "updated_at" field. 269 | func UpdatedAtLTE(v time.Time) predicate.User { 270 | return predicate.User(func(s *sql.Selector) { 271 | s.Where(sql.LTE(s.C(FieldUpdatedAt), v)) 272 | }) 273 | } 274 | 275 | // UsernameEQ applies the EQ predicate on the "username" field. 276 | func UsernameEQ(v string) predicate.User { 277 | return predicate.User(func(s *sql.Selector) { 278 | s.Where(sql.EQ(s.C(FieldUsername), v)) 279 | }) 280 | } 281 | 282 | // UsernameNEQ applies the NEQ predicate on the "username" field. 283 | func UsernameNEQ(v string) predicate.User { 284 | return predicate.User(func(s *sql.Selector) { 285 | s.Where(sql.NEQ(s.C(FieldUsername), v)) 286 | }) 287 | } 288 | 289 | // UsernameIn applies the In predicate on the "username" field. 290 | func UsernameIn(vs ...string) predicate.User { 291 | v := make([]interface{}, len(vs)) 292 | for i := range v { 293 | v[i] = vs[i] 294 | } 295 | return predicate.User(func(s *sql.Selector) { 296 | // if not arguments were provided, append the FALSE constants, 297 | // since we can't apply "IN ()". This will make this predicate falsy. 298 | if len(v) == 0 { 299 | s.Where(sql.False()) 300 | return 301 | } 302 | s.Where(sql.In(s.C(FieldUsername), v...)) 303 | }) 304 | } 305 | 306 | // UsernameNotIn applies the NotIn predicate on the "username" field. 307 | func UsernameNotIn(vs ...string) predicate.User { 308 | v := make([]interface{}, len(vs)) 309 | for i := range v { 310 | v[i] = vs[i] 311 | } 312 | return predicate.User(func(s *sql.Selector) { 313 | // if not arguments were provided, append the FALSE constants, 314 | // since we can't apply "IN ()". This will make this predicate falsy. 315 | if len(v) == 0 { 316 | s.Where(sql.False()) 317 | return 318 | } 319 | s.Where(sql.NotIn(s.C(FieldUsername), v...)) 320 | }) 321 | } 322 | 323 | // UsernameGT applies the GT predicate on the "username" field. 324 | func UsernameGT(v string) predicate.User { 325 | return predicate.User(func(s *sql.Selector) { 326 | s.Where(sql.GT(s.C(FieldUsername), v)) 327 | }) 328 | } 329 | 330 | // UsernameGTE applies the GTE predicate on the "username" field. 331 | func UsernameGTE(v string) predicate.User { 332 | return predicate.User(func(s *sql.Selector) { 333 | s.Where(sql.GTE(s.C(FieldUsername), v)) 334 | }) 335 | } 336 | 337 | // UsernameLT applies the LT predicate on the "username" field. 338 | func UsernameLT(v string) predicate.User { 339 | return predicate.User(func(s *sql.Selector) { 340 | s.Where(sql.LT(s.C(FieldUsername), v)) 341 | }) 342 | } 343 | 344 | // UsernameLTE applies the LTE predicate on the "username" field. 345 | func UsernameLTE(v string) predicate.User { 346 | return predicate.User(func(s *sql.Selector) { 347 | s.Where(sql.LTE(s.C(FieldUsername), v)) 348 | }) 349 | } 350 | 351 | // UsernameContains applies the Contains predicate on the "username" field. 352 | func UsernameContains(v string) predicate.User { 353 | return predicate.User(func(s *sql.Selector) { 354 | s.Where(sql.Contains(s.C(FieldUsername), v)) 355 | }) 356 | } 357 | 358 | // UsernameHasPrefix applies the HasPrefix predicate on the "username" field. 359 | func UsernameHasPrefix(v string) predicate.User { 360 | return predicate.User(func(s *sql.Selector) { 361 | s.Where(sql.HasPrefix(s.C(FieldUsername), v)) 362 | }) 363 | } 364 | 365 | // UsernameHasSuffix applies the HasSuffix predicate on the "username" field. 366 | func UsernameHasSuffix(v string) predicate.User { 367 | return predicate.User(func(s *sql.Selector) { 368 | s.Where(sql.HasSuffix(s.C(FieldUsername), v)) 369 | }) 370 | } 371 | 372 | // UsernameEqualFold applies the EqualFold predicate on the "username" field. 373 | func UsernameEqualFold(v string) predicate.User { 374 | return predicate.User(func(s *sql.Selector) { 375 | s.Where(sql.EqualFold(s.C(FieldUsername), v)) 376 | }) 377 | } 378 | 379 | // UsernameContainsFold applies the ContainsFold predicate on the "username" field. 380 | func UsernameContainsFold(v string) predicate.User { 381 | return predicate.User(func(s *sql.Selector) { 382 | s.Where(sql.ContainsFold(s.C(FieldUsername), v)) 383 | }) 384 | } 385 | 386 | // PasswordEQ applies the EQ predicate on the "password" field. 387 | func PasswordEQ(v string) predicate.User { 388 | return predicate.User(func(s *sql.Selector) { 389 | s.Where(sql.EQ(s.C(FieldPassword), v)) 390 | }) 391 | } 392 | 393 | // PasswordNEQ applies the NEQ predicate on the "password" field. 394 | func PasswordNEQ(v string) predicate.User { 395 | return predicate.User(func(s *sql.Selector) { 396 | s.Where(sql.NEQ(s.C(FieldPassword), v)) 397 | }) 398 | } 399 | 400 | // PasswordIn applies the In predicate on the "password" field. 401 | func PasswordIn(vs ...string) predicate.User { 402 | v := make([]interface{}, len(vs)) 403 | for i := range v { 404 | v[i] = vs[i] 405 | } 406 | return predicate.User(func(s *sql.Selector) { 407 | // if not arguments were provided, append the FALSE constants, 408 | // since we can't apply "IN ()". This will make this predicate falsy. 409 | if len(v) == 0 { 410 | s.Where(sql.False()) 411 | return 412 | } 413 | s.Where(sql.In(s.C(FieldPassword), v...)) 414 | }) 415 | } 416 | 417 | // PasswordNotIn applies the NotIn predicate on the "password" field. 418 | func PasswordNotIn(vs ...string) predicate.User { 419 | v := make([]interface{}, len(vs)) 420 | for i := range v { 421 | v[i] = vs[i] 422 | } 423 | return predicate.User(func(s *sql.Selector) { 424 | // if not arguments were provided, append the FALSE constants, 425 | // since we can't apply "IN ()". This will make this predicate falsy. 426 | if len(v) == 0 { 427 | s.Where(sql.False()) 428 | return 429 | } 430 | s.Where(sql.NotIn(s.C(FieldPassword), v...)) 431 | }) 432 | } 433 | 434 | // PasswordGT applies the GT predicate on the "password" field. 435 | func PasswordGT(v string) predicate.User { 436 | return predicate.User(func(s *sql.Selector) { 437 | s.Where(sql.GT(s.C(FieldPassword), v)) 438 | }) 439 | } 440 | 441 | // PasswordGTE applies the GTE predicate on the "password" field. 442 | func PasswordGTE(v string) predicate.User { 443 | return predicate.User(func(s *sql.Selector) { 444 | s.Where(sql.GTE(s.C(FieldPassword), v)) 445 | }) 446 | } 447 | 448 | // PasswordLT applies the LT predicate on the "password" field. 449 | func PasswordLT(v string) predicate.User { 450 | return predicate.User(func(s *sql.Selector) { 451 | s.Where(sql.LT(s.C(FieldPassword), v)) 452 | }) 453 | } 454 | 455 | // PasswordLTE applies the LTE predicate on the "password" field. 456 | func PasswordLTE(v string) predicate.User { 457 | return predicate.User(func(s *sql.Selector) { 458 | s.Where(sql.LTE(s.C(FieldPassword), v)) 459 | }) 460 | } 461 | 462 | // PasswordContains applies the Contains predicate on the "password" field. 463 | func PasswordContains(v string) predicate.User { 464 | return predicate.User(func(s *sql.Selector) { 465 | s.Where(sql.Contains(s.C(FieldPassword), v)) 466 | }) 467 | } 468 | 469 | // PasswordHasPrefix applies the HasPrefix predicate on the "password" field. 470 | func PasswordHasPrefix(v string) predicate.User { 471 | return predicate.User(func(s *sql.Selector) { 472 | s.Where(sql.HasPrefix(s.C(FieldPassword), v)) 473 | }) 474 | } 475 | 476 | // PasswordHasSuffix applies the HasSuffix predicate on the "password" field. 477 | func PasswordHasSuffix(v string) predicate.User { 478 | return predicate.User(func(s *sql.Selector) { 479 | s.Where(sql.HasSuffix(s.C(FieldPassword), v)) 480 | }) 481 | } 482 | 483 | // PasswordEqualFold applies the EqualFold predicate on the "password" field. 484 | func PasswordEqualFold(v string) predicate.User { 485 | return predicate.User(func(s *sql.Selector) { 486 | s.Where(sql.EqualFold(s.C(FieldPassword), v)) 487 | }) 488 | } 489 | 490 | // PasswordContainsFold applies the ContainsFold predicate on the "password" field. 491 | func PasswordContainsFold(v string) predicate.User { 492 | return predicate.User(func(s *sql.Selector) { 493 | s.Where(sql.ContainsFold(s.C(FieldPassword), v)) 494 | }) 495 | } 496 | 497 | // And groups predicates with the AND operator between them. 498 | func And(predicates ...predicate.User) predicate.User { 499 | return predicate.User(func(s *sql.Selector) { 500 | s1 := s.Clone().SetP(nil) 501 | for _, p := range predicates { 502 | p(s1) 503 | } 504 | s.Where(s1.P()) 505 | }) 506 | } 507 | 508 | // Or groups predicates with the OR operator between them. 509 | func Or(predicates ...predicate.User) predicate.User { 510 | return predicate.User(func(s *sql.Selector) { 511 | s1 := s.Clone().SetP(nil) 512 | for i, p := range predicates { 513 | if i > 0 { 514 | s1.Or() 515 | } 516 | p(s1) 517 | } 518 | s.Where(s1.P()) 519 | }) 520 | } 521 | 522 | // Not applies the not operator on the given predicate. 523 | func Not(p predicate.User) predicate.User { 524 | return predicate.User(func(s *sql.Selector) { 525 | p(s.Not()) 526 | }) 527 | } 528 | -------------------------------------------------------------------------------- /ent/user_create.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | "time" 10 | 11 | "entgo.io/ent/dialect/sql/sqlgraph" 12 | "entgo.io/ent/schema/field" 13 | "github.com/fitv/min/ent/user" 14 | ) 15 | 16 | // UserCreate is the builder for creating a User entity. 17 | type UserCreate struct { 18 | config 19 | mutation *UserMutation 20 | hooks []Hook 21 | } 22 | 23 | // SetCreatedAt sets the "created_at" field. 24 | func (uc *UserCreate) SetCreatedAt(t time.Time) *UserCreate { 25 | uc.mutation.SetCreatedAt(t) 26 | return uc 27 | } 28 | 29 | // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. 30 | func (uc *UserCreate) SetNillableCreatedAt(t *time.Time) *UserCreate { 31 | if t != nil { 32 | uc.SetCreatedAt(*t) 33 | } 34 | return uc 35 | } 36 | 37 | // SetUpdatedAt sets the "updated_at" field. 38 | func (uc *UserCreate) SetUpdatedAt(t time.Time) *UserCreate { 39 | uc.mutation.SetUpdatedAt(t) 40 | return uc 41 | } 42 | 43 | // SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. 44 | func (uc *UserCreate) SetNillableUpdatedAt(t *time.Time) *UserCreate { 45 | if t != nil { 46 | uc.SetUpdatedAt(*t) 47 | } 48 | return uc 49 | } 50 | 51 | // SetUsername sets the "username" field. 52 | func (uc *UserCreate) SetUsername(s string) *UserCreate { 53 | uc.mutation.SetUsername(s) 54 | return uc 55 | } 56 | 57 | // SetPassword sets the "password" field. 58 | func (uc *UserCreate) SetPassword(s string) *UserCreate { 59 | uc.mutation.SetPassword(s) 60 | return uc 61 | } 62 | 63 | // Mutation returns the UserMutation object of the builder. 64 | func (uc *UserCreate) Mutation() *UserMutation { 65 | return uc.mutation 66 | } 67 | 68 | // Save creates the User in the database. 69 | func (uc *UserCreate) Save(ctx context.Context) (*User, error) { 70 | var ( 71 | err error 72 | node *User 73 | ) 74 | if err := uc.defaults(); err != nil { 75 | return nil, err 76 | } 77 | if len(uc.hooks) == 0 { 78 | if err = uc.check(); err != nil { 79 | return nil, err 80 | } 81 | node, err = uc.sqlSave(ctx) 82 | } else { 83 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 84 | mutation, ok := m.(*UserMutation) 85 | if !ok { 86 | return nil, fmt.Errorf("unexpected mutation type %T", m) 87 | } 88 | if err = uc.check(); err != nil { 89 | return nil, err 90 | } 91 | uc.mutation = mutation 92 | if node, err = uc.sqlSave(ctx); err != nil { 93 | return nil, err 94 | } 95 | mutation.id = &node.ID 96 | mutation.done = true 97 | return node, err 98 | }) 99 | for i := len(uc.hooks) - 1; i >= 0; i-- { 100 | if uc.hooks[i] == nil { 101 | return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") 102 | } 103 | mut = uc.hooks[i](mut) 104 | } 105 | if _, err := mut.Mutate(ctx, uc.mutation); err != nil { 106 | return nil, err 107 | } 108 | } 109 | return node, err 110 | } 111 | 112 | // SaveX calls Save and panics if Save returns an error. 113 | func (uc *UserCreate) SaveX(ctx context.Context) *User { 114 | v, err := uc.Save(ctx) 115 | if err != nil { 116 | panic(err) 117 | } 118 | return v 119 | } 120 | 121 | // Exec executes the query. 122 | func (uc *UserCreate) Exec(ctx context.Context) error { 123 | _, err := uc.Save(ctx) 124 | return err 125 | } 126 | 127 | // ExecX is like Exec, but panics if an error occurs. 128 | func (uc *UserCreate) ExecX(ctx context.Context) { 129 | if err := uc.Exec(ctx); err != nil { 130 | panic(err) 131 | } 132 | } 133 | 134 | // defaults sets the default values of the builder before save. 135 | func (uc *UserCreate) defaults() error { 136 | if _, ok := uc.mutation.CreatedAt(); !ok { 137 | if user.DefaultCreatedAt == nil { 138 | return fmt.Errorf("ent: uninitialized user.DefaultCreatedAt (forgotten import ent/runtime?)") 139 | } 140 | v := user.DefaultCreatedAt() 141 | uc.mutation.SetCreatedAt(v) 142 | } 143 | if _, ok := uc.mutation.UpdatedAt(); !ok { 144 | if user.DefaultUpdatedAt == nil { 145 | return fmt.Errorf("ent: uninitialized user.DefaultUpdatedAt (forgotten import ent/runtime?)") 146 | } 147 | v := user.DefaultUpdatedAt() 148 | uc.mutation.SetUpdatedAt(v) 149 | } 150 | return nil 151 | } 152 | 153 | // check runs all checks and user-defined validators on the builder. 154 | func (uc *UserCreate) check() error { 155 | if _, ok := uc.mutation.CreatedAt(); !ok { 156 | return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "created_at"`)} 157 | } 158 | if _, ok := uc.mutation.UpdatedAt(); !ok { 159 | return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "updated_at"`)} 160 | } 161 | if _, ok := uc.mutation.Username(); !ok { 162 | return &ValidationError{Name: "username", err: errors.New(`ent: missing required field "username"`)} 163 | } 164 | if _, ok := uc.mutation.Password(); !ok { 165 | return &ValidationError{Name: "password", err: errors.New(`ent: missing required field "password"`)} 166 | } 167 | return nil 168 | } 169 | 170 | func (uc *UserCreate) sqlSave(ctx context.Context) (*User, error) { 171 | _node, _spec := uc.createSpec() 172 | if err := sqlgraph.CreateNode(ctx, uc.driver, _spec); err != nil { 173 | if sqlgraph.IsConstraintError(err) { 174 | err = &ConstraintError{err.Error(), err} 175 | } 176 | return nil, err 177 | } 178 | id := _spec.ID.Value.(int64) 179 | _node.ID = int(id) 180 | return _node, nil 181 | } 182 | 183 | func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { 184 | var ( 185 | _node = &User{config: uc.config} 186 | _spec = &sqlgraph.CreateSpec{ 187 | Table: user.Table, 188 | ID: &sqlgraph.FieldSpec{ 189 | Type: field.TypeInt, 190 | Column: user.FieldID, 191 | }, 192 | } 193 | ) 194 | if value, ok := uc.mutation.CreatedAt(); ok { 195 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 196 | Type: field.TypeTime, 197 | Value: value, 198 | Column: user.FieldCreatedAt, 199 | }) 200 | _node.CreatedAt = value 201 | } 202 | if value, ok := uc.mutation.UpdatedAt(); ok { 203 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 204 | Type: field.TypeTime, 205 | Value: value, 206 | Column: user.FieldUpdatedAt, 207 | }) 208 | _node.UpdatedAt = value 209 | } 210 | if value, ok := uc.mutation.Username(); ok { 211 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 212 | Type: field.TypeString, 213 | Value: value, 214 | Column: user.FieldUsername, 215 | }) 216 | _node.Username = value 217 | } 218 | if value, ok := uc.mutation.Password(); ok { 219 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 220 | Type: field.TypeString, 221 | Value: value, 222 | Column: user.FieldPassword, 223 | }) 224 | _node.Password = value 225 | } 226 | return _node, _spec 227 | } 228 | 229 | // UserCreateBulk is the builder for creating many User entities in bulk. 230 | type UserCreateBulk struct { 231 | config 232 | builders []*UserCreate 233 | } 234 | 235 | // Save creates the User entities in the database. 236 | func (ucb *UserCreateBulk) Save(ctx context.Context) ([]*User, error) { 237 | specs := make([]*sqlgraph.CreateSpec, len(ucb.builders)) 238 | nodes := make([]*User, len(ucb.builders)) 239 | mutators := make([]Mutator, len(ucb.builders)) 240 | for i := range ucb.builders { 241 | func(i int, root context.Context) { 242 | builder := ucb.builders[i] 243 | builder.defaults() 244 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 245 | mutation, ok := m.(*UserMutation) 246 | if !ok { 247 | return nil, fmt.Errorf("unexpected mutation type %T", m) 248 | } 249 | if err := builder.check(); err != nil { 250 | return nil, err 251 | } 252 | builder.mutation = mutation 253 | nodes[i], specs[i] = builder.createSpec() 254 | var err error 255 | if i < len(mutators)-1 { 256 | _, err = mutators[i+1].Mutate(root, ucb.builders[i+1].mutation) 257 | } else { 258 | spec := &sqlgraph.BatchCreateSpec{Nodes: specs} 259 | // Invoke the actual operation on the latest mutation in the chain. 260 | if err = sqlgraph.BatchCreate(ctx, ucb.driver, spec); err != nil { 261 | if sqlgraph.IsConstraintError(err) { 262 | err = &ConstraintError{err.Error(), err} 263 | } 264 | } 265 | } 266 | if err != nil { 267 | return nil, err 268 | } 269 | mutation.id = &nodes[i].ID 270 | mutation.done = true 271 | if specs[i].ID.Value != nil { 272 | id := specs[i].ID.Value.(int64) 273 | nodes[i].ID = int(id) 274 | } 275 | return nodes[i], nil 276 | }) 277 | for i := len(builder.hooks) - 1; i >= 0; i-- { 278 | mut = builder.hooks[i](mut) 279 | } 280 | mutators[i] = mut 281 | }(i, ctx) 282 | } 283 | if len(mutators) > 0 { 284 | if _, err := mutators[0].Mutate(ctx, ucb.builders[0].mutation); err != nil { 285 | return nil, err 286 | } 287 | } 288 | return nodes, nil 289 | } 290 | 291 | // SaveX is like Save, but panics if an error occurs. 292 | func (ucb *UserCreateBulk) SaveX(ctx context.Context) []*User { 293 | v, err := ucb.Save(ctx) 294 | if err != nil { 295 | panic(err) 296 | } 297 | return v 298 | } 299 | 300 | // Exec executes the query. 301 | func (ucb *UserCreateBulk) Exec(ctx context.Context) error { 302 | _, err := ucb.Save(ctx) 303 | return err 304 | } 305 | 306 | // ExecX is like Exec, but panics if an error occurs. 307 | func (ucb *UserCreateBulk) ExecX(ctx context.Context) { 308 | if err := ucb.Exec(ctx); err != nil { 309 | panic(err) 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /ent/user_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | 9 | "entgo.io/ent/dialect/sql" 10 | "entgo.io/ent/dialect/sql/sqlgraph" 11 | "entgo.io/ent/schema/field" 12 | "github.com/fitv/min/ent/predicate" 13 | "github.com/fitv/min/ent/user" 14 | ) 15 | 16 | // UserDelete is the builder for deleting a User entity. 17 | type UserDelete struct { 18 | config 19 | hooks []Hook 20 | mutation *UserMutation 21 | } 22 | 23 | // Where appends a list predicates to the UserDelete builder. 24 | func (ud *UserDelete) Where(ps ...predicate.User) *UserDelete { 25 | ud.mutation.Where(ps...) 26 | return ud 27 | } 28 | 29 | // Exec executes the deletion query and returns how many vertices were deleted. 30 | func (ud *UserDelete) Exec(ctx context.Context) (int, error) { 31 | var ( 32 | err error 33 | affected int 34 | ) 35 | if len(ud.hooks) == 0 { 36 | affected, err = ud.sqlExec(ctx) 37 | } else { 38 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 39 | mutation, ok := m.(*UserMutation) 40 | if !ok { 41 | return nil, fmt.Errorf("unexpected mutation type %T", m) 42 | } 43 | ud.mutation = mutation 44 | affected, err = ud.sqlExec(ctx) 45 | mutation.done = true 46 | return affected, err 47 | }) 48 | for i := len(ud.hooks) - 1; i >= 0; i-- { 49 | if ud.hooks[i] == nil { 50 | return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") 51 | } 52 | mut = ud.hooks[i](mut) 53 | } 54 | if _, err := mut.Mutate(ctx, ud.mutation); err != nil { 55 | return 0, err 56 | } 57 | } 58 | return affected, err 59 | } 60 | 61 | // ExecX is like Exec, but panics if an error occurs. 62 | func (ud *UserDelete) ExecX(ctx context.Context) int { 63 | n, err := ud.Exec(ctx) 64 | if err != nil { 65 | panic(err) 66 | } 67 | return n 68 | } 69 | 70 | func (ud *UserDelete) sqlExec(ctx context.Context) (int, error) { 71 | _spec := &sqlgraph.DeleteSpec{ 72 | Node: &sqlgraph.NodeSpec{ 73 | Table: user.Table, 74 | ID: &sqlgraph.FieldSpec{ 75 | Type: field.TypeInt, 76 | Column: user.FieldID, 77 | }, 78 | }, 79 | } 80 | if ps := ud.mutation.predicates; len(ps) > 0 { 81 | _spec.Predicate = func(selector *sql.Selector) { 82 | for i := range ps { 83 | ps[i](selector) 84 | } 85 | } 86 | } 87 | return sqlgraph.DeleteNodes(ctx, ud.driver, _spec) 88 | } 89 | 90 | // UserDeleteOne is the builder for deleting a single User entity. 91 | type UserDeleteOne struct { 92 | ud *UserDelete 93 | } 94 | 95 | // Exec executes the deletion query. 96 | func (udo *UserDeleteOne) Exec(ctx context.Context) error { 97 | n, err := udo.ud.Exec(ctx) 98 | switch { 99 | case err != nil: 100 | return err 101 | case n == 0: 102 | return &NotFoundError{user.Label} 103 | default: 104 | return nil 105 | } 106 | } 107 | 108 | // ExecX is like Exec, but panics if an error occurs. 109 | func (udo *UserDeleteOne) ExecX(ctx context.Context) { 110 | udo.ud.ExecX(ctx) 111 | } 112 | -------------------------------------------------------------------------------- /ent/user_query.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | "math" 10 | 11 | "entgo.io/ent/dialect" 12 | "entgo.io/ent/dialect/sql" 13 | "entgo.io/ent/dialect/sql/sqlgraph" 14 | "entgo.io/ent/schema/field" 15 | "github.com/fitv/min/ent/predicate" 16 | "github.com/fitv/min/ent/user" 17 | ) 18 | 19 | // UserQuery is the builder for querying User entities. 20 | type UserQuery struct { 21 | config 22 | limit *int 23 | offset *int 24 | unique *bool 25 | order []OrderFunc 26 | fields []string 27 | predicates []predicate.User 28 | modifiers []func(s *sql.Selector) 29 | // intermediate query (i.e. traversal path). 30 | sql *sql.Selector 31 | path func(context.Context) (*sql.Selector, error) 32 | } 33 | 34 | // Where adds a new predicate for the UserQuery builder. 35 | func (uq *UserQuery) Where(ps ...predicate.User) *UserQuery { 36 | uq.predicates = append(uq.predicates, ps...) 37 | return uq 38 | } 39 | 40 | // Limit adds a limit step to the query. 41 | func (uq *UserQuery) Limit(limit int) *UserQuery { 42 | uq.limit = &limit 43 | return uq 44 | } 45 | 46 | // Offset adds an offset step to the query. 47 | func (uq *UserQuery) Offset(offset int) *UserQuery { 48 | uq.offset = &offset 49 | return uq 50 | } 51 | 52 | // Unique configures the query builder to filter duplicate records on query. 53 | // By default, unique is set to true, and can be disabled using this method. 54 | func (uq *UserQuery) Unique(unique bool) *UserQuery { 55 | uq.unique = &unique 56 | return uq 57 | } 58 | 59 | // Order adds an order step to the query. 60 | func (uq *UserQuery) Order(o ...OrderFunc) *UserQuery { 61 | uq.order = append(uq.order, o...) 62 | return uq 63 | } 64 | 65 | // First returns the first User entity from the query. 66 | // Returns a *NotFoundError when no User was found. 67 | func (uq *UserQuery) First(ctx context.Context) (*User, error) { 68 | nodes, err := uq.Limit(1).All(ctx) 69 | if err != nil { 70 | return nil, err 71 | } 72 | if len(nodes) == 0 { 73 | return nil, &NotFoundError{user.Label} 74 | } 75 | return nodes[0], nil 76 | } 77 | 78 | // FirstX is like First, but panics if an error occurs. 79 | func (uq *UserQuery) FirstX(ctx context.Context) *User { 80 | node, err := uq.First(ctx) 81 | if err != nil && !IsNotFound(err) { 82 | panic(err) 83 | } 84 | return node 85 | } 86 | 87 | // FirstID returns the first User ID from the query. 88 | // Returns a *NotFoundError when no User ID was found. 89 | func (uq *UserQuery) FirstID(ctx context.Context) (id int, err error) { 90 | var ids []int 91 | if ids, err = uq.Limit(1).IDs(ctx); err != nil { 92 | return 93 | } 94 | if len(ids) == 0 { 95 | err = &NotFoundError{user.Label} 96 | return 97 | } 98 | return ids[0], nil 99 | } 100 | 101 | // FirstIDX is like FirstID, but panics if an error occurs. 102 | func (uq *UserQuery) FirstIDX(ctx context.Context) int { 103 | id, err := uq.FirstID(ctx) 104 | if err != nil && !IsNotFound(err) { 105 | panic(err) 106 | } 107 | return id 108 | } 109 | 110 | // Only returns a single User entity found by the query, ensuring it only returns one. 111 | // Returns a *NotSingularError when exactly one User entity is not found. 112 | // Returns a *NotFoundError when no User entities are found. 113 | func (uq *UserQuery) Only(ctx context.Context) (*User, error) { 114 | nodes, err := uq.Limit(2).All(ctx) 115 | if err != nil { 116 | return nil, err 117 | } 118 | switch len(nodes) { 119 | case 1: 120 | return nodes[0], nil 121 | case 0: 122 | return nil, &NotFoundError{user.Label} 123 | default: 124 | return nil, &NotSingularError{user.Label} 125 | } 126 | } 127 | 128 | // OnlyX is like Only, but panics if an error occurs. 129 | func (uq *UserQuery) OnlyX(ctx context.Context) *User { 130 | node, err := uq.Only(ctx) 131 | if err != nil { 132 | panic(err) 133 | } 134 | return node 135 | } 136 | 137 | // OnlyID is like Only, but returns the only User ID in the query. 138 | // Returns a *NotSingularError when exactly one User ID is not found. 139 | // Returns a *NotFoundError when no entities are found. 140 | func (uq *UserQuery) OnlyID(ctx context.Context) (id int, err error) { 141 | var ids []int 142 | if ids, err = uq.Limit(2).IDs(ctx); err != nil { 143 | return 144 | } 145 | switch len(ids) { 146 | case 1: 147 | id = ids[0] 148 | case 0: 149 | err = &NotFoundError{user.Label} 150 | default: 151 | err = &NotSingularError{user.Label} 152 | } 153 | return 154 | } 155 | 156 | // OnlyIDX is like OnlyID, but panics if an error occurs. 157 | func (uq *UserQuery) OnlyIDX(ctx context.Context) int { 158 | id, err := uq.OnlyID(ctx) 159 | if err != nil { 160 | panic(err) 161 | } 162 | return id 163 | } 164 | 165 | // All executes the query and returns a list of Users. 166 | func (uq *UserQuery) All(ctx context.Context) ([]*User, error) { 167 | if err := uq.prepareQuery(ctx); err != nil { 168 | return nil, err 169 | } 170 | return uq.sqlAll(ctx) 171 | } 172 | 173 | // AllX is like All, but panics if an error occurs. 174 | func (uq *UserQuery) AllX(ctx context.Context) []*User { 175 | nodes, err := uq.All(ctx) 176 | if err != nil { 177 | panic(err) 178 | } 179 | return nodes 180 | } 181 | 182 | // IDs executes the query and returns a list of User IDs. 183 | func (uq *UserQuery) IDs(ctx context.Context) ([]int, error) { 184 | var ids []int 185 | if err := uq.Select(user.FieldID).Scan(ctx, &ids); err != nil { 186 | return nil, err 187 | } 188 | return ids, nil 189 | } 190 | 191 | // IDsX is like IDs, but panics if an error occurs. 192 | func (uq *UserQuery) IDsX(ctx context.Context) []int { 193 | ids, err := uq.IDs(ctx) 194 | if err != nil { 195 | panic(err) 196 | } 197 | return ids 198 | } 199 | 200 | // Count returns the count of the given query. 201 | func (uq *UserQuery) Count(ctx context.Context) (int, error) { 202 | if err := uq.prepareQuery(ctx); err != nil { 203 | return 0, err 204 | } 205 | return uq.sqlCount(ctx) 206 | } 207 | 208 | // CountX is like Count, but panics if an error occurs. 209 | func (uq *UserQuery) CountX(ctx context.Context) int { 210 | count, err := uq.Count(ctx) 211 | if err != nil { 212 | panic(err) 213 | } 214 | return count 215 | } 216 | 217 | // Exist returns true if the query has elements in the graph. 218 | func (uq *UserQuery) Exist(ctx context.Context) (bool, error) { 219 | if err := uq.prepareQuery(ctx); err != nil { 220 | return false, err 221 | } 222 | return uq.sqlExist(ctx) 223 | } 224 | 225 | // ExistX is like Exist, but panics if an error occurs. 226 | func (uq *UserQuery) ExistX(ctx context.Context) bool { 227 | exist, err := uq.Exist(ctx) 228 | if err != nil { 229 | panic(err) 230 | } 231 | return exist 232 | } 233 | 234 | // Clone returns a duplicate of the UserQuery builder, including all associated steps. It can be 235 | // used to prepare common query builders and use them differently after the clone is made. 236 | func (uq *UserQuery) Clone() *UserQuery { 237 | if uq == nil { 238 | return nil 239 | } 240 | return &UserQuery{ 241 | config: uq.config, 242 | limit: uq.limit, 243 | offset: uq.offset, 244 | order: append([]OrderFunc{}, uq.order...), 245 | predicates: append([]predicate.User{}, uq.predicates...), 246 | // clone intermediate query. 247 | sql: uq.sql.Clone(), 248 | path: uq.path, 249 | } 250 | } 251 | 252 | // GroupBy is used to group vertices by one or more fields/columns. 253 | // It is often used with aggregate functions, like: count, max, mean, min, sum. 254 | // 255 | // Example: 256 | // 257 | // var v []struct { 258 | // CreatedAt time.Time `json:"created_at,omitempty"` 259 | // Count int `json:"count,omitempty"` 260 | // } 261 | // 262 | // client.User.Query(). 263 | // GroupBy(user.FieldCreatedAt). 264 | // Aggregate(ent.Count()). 265 | // Scan(ctx, &v) 266 | // 267 | func (uq *UserQuery) GroupBy(field string, fields ...string) *UserGroupBy { 268 | group := &UserGroupBy{config: uq.config} 269 | group.fields = append([]string{field}, fields...) 270 | group.path = func(ctx context.Context) (prev *sql.Selector, err error) { 271 | if err := uq.prepareQuery(ctx); err != nil { 272 | return nil, err 273 | } 274 | return uq.sqlQuery(ctx), nil 275 | } 276 | return group 277 | } 278 | 279 | // Select allows the selection one or more fields/columns for the given query, 280 | // instead of selecting all fields in the entity. 281 | // 282 | // Example: 283 | // 284 | // var v []struct { 285 | // CreatedAt time.Time `json:"created_at,omitempty"` 286 | // } 287 | // 288 | // client.User.Query(). 289 | // Select(user.FieldCreatedAt). 290 | // Scan(ctx, &v) 291 | // 292 | func (uq *UserQuery) Select(fields ...string) *UserSelect { 293 | uq.fields = append(uq.fields, fields...) 294 | return &UserSelect{UserQuery: uq} 295 | } 296 | 297 | func (uq *UserQuery) prepareQuery(ctx context.Context) error { 298 | for _, f := range uq.fields { 299 | if !user.ValidColumn(f) { 300 | return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} 301 | } 302 | } 303 | if uq.path != nil { 304 | prev, err := uq.path(ctx) 305 | if err != nil { 306 | return err 307 | } 308 | uq.sql = prev 309 | } 310 | return nil 311 | } 312 | 313 | func (uq *UserQuery) sqlAll(ctx context.Context) ([]*User, error) { 314 | var ( 315 | nodes = []*User{} 316 | _spec = uq.querySpec() 317 | ) 318 | _spec.ScanValues = func(columns []string) ([]interface{}, error) { 319 | node := &User{config: uq.config} 320 | nodes = append(nodes, node) 321 | return node.scanValues(columns) 322 | } 323 | _spec.Assign = func(columns []string, values []interface{}) error { 324 | if len(nodes) == 0 { 325 | return fmt.Errorf("ent: Assign called without calling ScanValues") 326 | } 327 | node := nodes[len(nodes)-1] 328 | return node.assignValues(columns, values) 329 | } 330 | if len(uq.modifiers) > 0 { 331 | _spec.Modifiers = uq.modifiers 332 | } 333 | if err := sqlgraph.QueryNodes(ctx, uq.driver, _spec); err != nil { 334 | return nil, err 335 | } 336 | if len(nodes) == 0 { 337 | return nodes, nil 338 | } 339 | return nodes, nil 340 | } 341 | 342 | func (uq *UserQuery) sqlCount(ctx context.Context) (int, error) { 343 | _spec := uq.querySpec() 344 | if len(uq.modifiers) > 0 { 345 | _spec.Modifiers = uq.modifiers 346 | } 347 | return sqlgraph.CountNodes(ctx, uq.driver, _spec) 348 | } 349 | 350 | func (uq *UserQuery) sqlExist(ctx context.Context) (bool, error) { 351 | n, err := uq.sqlCount(ctx) 352 | if err != nil { 353 | return false, fmt.Errorf("ent: check existence: %w", err) 354 | } 355 | return n > 0, nil 356 | } 357 | 358 | func (uq *UserQuery) querySpec() *sqlgraph.QuerySpec { 359 | _spec := &sqlgraph.QuerySpec{ 360 | Node: &sqlgraph.NodeSpec{ 361 | Table: user.Table, 362 | Columns: user.Columns, 363 | ID: &sqlgraph.FieldSpec{ 364 | Type: field.TypeInt, 365 | Column: user.FieldID, 366 | }, 367 | }, 368 | From: uq.sql, 369 | Unique: true, 370 | } 371 | if unique := uq.unique; unique != nil { 372 | _spec.Unique = *unique 373 | } 374 | if fields := uq.fields; len(fields) > 0 { 375 | _spec.Node.Columns = make([]string, 0, len(fields)) 376 | _spec.Node.Columns = append(_spec.Node.Columns, user.FieldID) 377 | for i := range fields { 378 | if fields[i] != user.FieldID { 379 | _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) 380 | } 381 | } 382 | } 383 | if ps := uq.predicates; len(ps) > 0 { 384 | _spec.Predicate = func(selector *sql.Selector) { 385 | for i := range ps { 386 | ps[i](selector) 387 | } 388 | } 389 | } 390 | if limit := uq.limit; limit != nil { 391 | _spec.Limit = *limit 392 | } 393 | if offset := uq.offset; offset != nil { 394 | _spec.Offset = *offset 395 | } 396 | if ps := uq.order; len(ps) > 0 { 397 | _spec.Order = func(selector *sql.Selector) { 398 | for i := range ps { 399 | ps[i](selector) 400 | } 401 | } 402 | } 403 | return _spec 404 | } 405 | 406 | func (uq *UserQuery) sqlQuery(ctx context.Context) *sql.Selector { 407 | builder := sql.Dialect(uq.driver.Dialect()) 408 | t1 := builder.Table(user.Table) 409 | columns := uq.fields 410 | if len(columns) == 0 { 411 | columns = user.Columns 412 | } 413 | selector := builder.Select(t1.Columns(columns...)...).From(t1) 414 | if uq.sql != nil { 415 | selector = uq.sql 416 | selector.Select(selector.Columns(columns...)...) 417 | } 418 | for _, m := range uq.modifiers { 419 | m(selector) 420 | } 421 | for _, p := range uq.predicates { 422 | p(selector) 423 | } 424 | for _, p := range uq.order { 425 | p(selector) 426 | } 427 | if offset := uq.offset; offset != nil { 428 | // limit is mandatory for offset clause. We start 429 | // with default value, and override it below if needed. 430 | selector.Offset(*offset).Limit(math.MaxInt32) 431 | } 432 | if limit := uq.limit; limit != nil { 433 | selector.Limit(*limit) 434 | } 435 | return selector 436 | } 437 | 438 | // ForUpdate locks the selected rows against concurrent updates, and prevent them from being 439 | // updated, deleted or "selected ... for update" by other sessions, until the transaction is 440 | // either committed or rolled-back. 441 | func (uq *UserQuery) ForUpdate(opts ...sql.LockOption) *UserQuery { 442 | if uq.driver.Dialect() == dialect.Postgres { 443 | uq.Unique(false) 444 | } 445 | uq.modifiers = append(uq.modifiers, func(s *sql.Selector) { 446 | s.ForUpdate(opts...) 447 | }) 448 | return uq 449 | } 450 | 451 | // ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock 452 | // on any rows that are read. Other sessions can read the rows, but cannot modify them 453 | // until your transaction commits. 454 | func (uq *UserQuery) ForShare(opts ...sql.LockOption) *UserQuery { 455 | if uq.driver.Dialect() == dialect.Postgres { 456 | uq.Unique(false) 457 | } 458 | uq.modifiers = append(uq.modifiers, func(s *sql.Selector) { 459 | s.ForShare(opts...) 460 | }) 461 | return uq 462 | } 463 | 464 | // UserGroupBy is the group-by builder for User entities. 465 | type UserGroupBy struct { 466 | config 467 | fields []string 468 | fns []AggregateFunc 469 | // intermediate query (i.e. traversal path). 470 | sql *sql.Selector 471 | path func(context.Context) (*sql.Selector, error) 472 | } 473 | 474 | // Aggregate adds the given aggregation functions to the group-by query. 475 | func (ugb *UserGroupBy) Aggregate(fns ...AggregateFunc) *UserGroupBy { 476 | ugb.fns = append(ugb.fns, fns...) 477 | return ugb 478 | } 479 | 480 | // Scan applies the group-by query and scans the result into the given value. 481 | func (ugb *UserGroupBy) Scan(ctx context.Context, v interface{}) error { 482 | query, err := ugb.path(ctx) 483 | if err != nil { 484 | return err 485 | } 486 | ugb.sql = query 487 | return ugb.sqlScan(ctx, v) 488 | } 489 | 490 | // ScanX is like Scan, but panics if an error occurs. 491 | func (ugb *UserGroupBy) ScanX(ctx context.Context, v interface{}) { 492 | if err := ugb.Scan(ctx, v); err != nil { 493 | panic(err) 494 | } 495 | } 496 | 497 | // Strings returns list of strings from group-by. 498 | // It is only allowed when executing a group-by query with one field. 499 | func (ugb *UserGroupBy) Strings(ctx context.Context) ([]string, error) { 500 | if len(ugb.fields) > 1 { 501 | return nil, errors.New("ent: UserGroupBy.Strings is not achievable when grouping more than 1 field") 502 | } 503 | var v []string 504 | if err := ugb.Scan(ctx, &v); err != nil { 505 | return nil, err 506 | } 507 | return v, nil 508 | } 509 | 510 | // StringsX is like Strings, but panics if an error occurs. 511 | func (ugb *UserGroupBy) StringsX(ctx context.Context) []string { 512 | v, err := ugb.Strings(ctx) 513 | if err != nil { 514 | panic(err) 515 | } 516 | return v 517 | } 518 | 519 | // String returns a single string from a group-by query. 520 | // It is only allowed when executing a group-by query with one field. 521 | func (ugb *UserGroupBy) String(ctx context.Context) (_ string, err error) { 522 | var v []string 523 | if v, err = ugb.Strings(ctx); err != nil { 524 | return 525 | } 526 | switch len(v) { 527 | case 1: 528 | return v[0], nil 529 | case 0: 530 | err = &NotFoundError{user.Label} 531 | default: 532 | err = fmt.Errorf("ent: UserGroupBy.Strings returned %d results when one was expected", len(v)) 533 | } 534 | return 535 | } 536 | 537 | // StringX is like String, but panics if an error occurs. 538 | func (ugb *UserGroupBy) StringX(ctx context.Context) string { 539 | v, err := ugb.String(ctx) 540 | if err != nil { 541 | panic(err) 542 | } 543 | return v 544 | } 545 | 546 | // Ints returns list of ints from group-by. 547 | // It is only allowed when executing a group-by query with one field. 548 | func (ugb *UserGroupBy) Ints(ctx context.Context) ([]int, error) { 549 | if len(ugb.fields) > 1 { 550 | return nil, errors.New("ent: UserGroupBy.Ints is not achievable when grouping more than 1 field") 551 | } 552 | var v []int 553 | if err := ugb.Scan(ctx, &v); err != nil { 554 | return nil, err 555 | } 556 | return v, nil 557 | } 558 | 559 | // IntsX is like Ints, but panics if an error occurs. 560 | func (ugb *UserGroupBy) IntsX(ctx context.Context) []int { 561 | v, err := ugb.Ints(ctx) 562 | if err != nil { 563 | panic(err) 564 | } 565 | return v 566 | } 567 | 568 | // Int returns a single int from a group-by query. 569 | // It is only allowed when executing a group-by query with one field. 570 | func (ugb *UserGroupBy) Int(ctx context.Context) (_ int, err error) { 571 | var v []int 572 | if v, err = ugb.Ints(ctx); err != nil { 573 | return 574 | } 575 | switch len(v) { 576 | case 1: 577 | return v[0], nil 578 | case 0: 579 | err = &NotFoundError{user.Label} 580 | default: 581 | err = fmt.Errorf("ent: UserGroupBy.Ints returned %d results when one was expected", len(v)) 582 | } 583 | return 584 | } 585 | 586 | // IntX is like Int, but panics if an error occurs. 587 | func (ugb *UserGroupBy) IntX(ctx context.Context) int { 588 | v, err := ugb.Int(ctx) 589 | if err != nil { 590 | panic(err) 591 | } 592 | return v 593 | } 594 | 595 | // Float64s returns list of float64s from group-by. 596 | // It is only allowed when executing a group-by query with one field. 597 | func (ugb *UserGroupBy) Float64s(ctx context.Context) ([]float64, error) { 598 | if len(ugb.fields) > 1 { 599 | return nil, errors.New("ent: UserGroupBy.Float64s is not achievable when grouping more than 1 field") 600 | } 601 | var v []float64 602 | if err := ugb.Scan(ctx, &v); err != nil { 603 | return nil, err 604 | } 605 | return v, nil 606 | } 607 | 608 | // Float64sX is like Float64s, but panics if an error occurs. 609 | func (ugb *UserGroupBy) Float64sX(ctx context.Context) []float64 { 610 | v, err := ugb.Float64s(ctx) 611 | if err != nil { 612 | panic(err) 613 | } 614 | return v 615 | } 616 | 617 | // Float64 returns a single float64 from a group-by query. 618 | // It is only allowed when executing a group-by query with one field. 619 | func (ugb *UserGroupBy) Float64(ctx context.Context) (_ float64, err error) { 620 | var v []float64 621 | if v, err = ugb.Float64s(ctx); err != nil { 622 | return 623 | } 624 | switch len(v) { 625 | case 1: 626 | return v[0], nil 627 | case 0: 628 | err = &NotFoundError{user.Label} 629 | default: 630 | err = fmt.Errorf("ent: UserGroupBy.Float64s returned %d results when one was expected", len(v)) 631 | } 632 | return 633 | } 634 | 635 | // Float64X is like Float64, but panics if an error occurs. 636 | func (ugb *UserGroupBy) Float64X(ctx context.Context) float64 { 637 | v, err := ugb.Float64(ctx) 638 | if err != nil { 639 | panic(err) 640 | } 641 | return v 642 | } 643 | 644 | // Bools returns list of bools from group-by. 645 | // It is only allowed when executing a group-by query with one field. 646 | func (ugb *UserGroupBy) Bools(ctx context.Context) ([]bool, error) { 647 | if len(ugb.fields) > 1 { 648 | return nil, errors.New("ent: UserGroupBy.Bools is not achievable when grouping more than 1 field") 649 | } 650 | var v []bool 651 | if err := ugb.Scan(ctx, &v); err != nil { 652 | return nil, err 653 | } 654 | return v, nil 655 | } 656 | 657 | // BoolsX is like Bools, but panics if an error occurs. 658 | func (ugb *UserGroupBy) BoolsX(ctx context.Context) []bool { 659 | v, err := ugb.Bools(ctx) 660 | if err != nil { 661 | panic(err) 662 | } 663 | return v 664 | } 665 | 666 | // Bool returns a single bool from a group-by query. 667 | // It is only allowed when executing a group-by query with one field. 668 | func (ugb *UserGroupBy) Bool(ctx context.Context) (_ bool, err error) { 669 | var v []bool 670 | if v, err = ugb.Bools(ctx); err != nil { 671 | return 672 | } 673 | switch len(v) { 674 | case 1: 675 | return v[0], nil 676 | case 0: 677 | err = &NotFoundError{user.Label} 678 | default: 679 | err = fmt.Errorf("ent: UserGroupBy.Bools returned %d results when one was expected", len(v)) 680 | } 681 | return 682 | } 683 | 684 | // BoolX is like Bool, but panics if an error occurs. 685 | func (ugb *UserGroupBy) BoolX(ctx context.Context) bool { 686 | v, err := ugb.Bool(ctx) 687 | if err != nil { 688 | panic(err) 689 | } 690 | return v 691 | } 692 | 693 | func (ugb *UserGroupBy) sqlScan(ctx context.Context, v interface{}) error { 694 | for _, f := range ugb.fields { 695 | if !user.ValidColumn(f) { 696 | return &ValidationError{Name: f, err: fmt.Errorf("invalid field %q for group-by", f)} 697 | } 698 | } 699 | selector := ugb.sqlQuery() 700 | if err := selector.Err(); err != nil { 701 | return err 702 | } 703 | rows := &sql.Rows{} 704 | query, args := selector.Query() 705 | if err := ugb.driver.Query(ctx, query, args, rows); err != nil { 706 | return err 707 | } 708 | defer rows.Close() 709 | return sql.ScanSlice(rows, v) 710 | } 711 | 712 | func (ugb *UserGroupBy) sqlQuery() *sql.Selector { 713 | selector := ugb.sql.Select() 714 | aggregation := make([]string, 0, len(ugb.fns)) 715 | for _, fn := range ugb.fns { 716 | aggregation = append(aggregation, fn(selector)) 717 | } 718 | // If no columns were selected in a custom aggregation function, the default 719 | // selection is the fields used for "group-by", and the aggregation functions. 720 | if len(selector.SelectedColumns()) == 0 { 721 | columns := make([]string, 0, len(ugb.fields)+len(ugb.fns)) 722 | for _, f := range ugb.fields { 723 | columns = append(columns, selector.C(f)) 724 | } 725 | for _, c := range aggregation { 726 | columns = append(columns, c) 727 | } 728 | selector.Select(columns...) 729 | } 730 | return selector.GroupBy(selector.Columns(ugb.fields...)...) 731 | } 732 | 733 | // UserSelect is the builder for selecting fields of User entities. 734 | type UserSelect struct { 735 | *UserQuery 736 | // intermediate query (i.e. traversal path). 737 | sql *sql.Selector 738 | } 739 | 740 | // Scan applies the selector query and scans the result into the given value. 741 | func (us *UserSelect) Scan(ctx context.Context, v interface{}) error { 742 | if err := us.prepareQuery(ctx); err != nil { 743 | return err 744 | } 745 | us.sql = us.UserQuery.sqlQuery(ctx) 746 | return us.sqlScan(ctx, v) 747 | } 748 | 749 | // ScanX is like Scan, but panics if an error occurs. 750 | func (us *UserSelect) ScanX(ctx context.Context, v interface{}) { 751 | if err := us.Scan(ctx, v); err != nil { 752 | panic(err) 753 | } 754 | } 755 | 756 | // Strings returns list of strings from a selector. It is only allowed when selecting one field. 757 | func (us *UserSelect) Strings(ctx context.Context) ([]string, error) { 758 | if len(us.fields) > 1 { 759 | return nil, errors.New("ent: UserSelect.Strings is not achievable when selecting more than 1 field") 760 | } 761 | var v []string 762 | if err := us.Scan(ctx, &v); err != nil { 763 | return nil, err 764 | } 765 | return v, nil 766 | } 767 | 768 | // StringsX is like Strings, but panics if an error occurs. 769 | func (us *UserSelect) StringsX(ctx context.Context) []string { 770 | v, err := us.Strings(ctx) 771 | if err != nil { 772 | panic(err) 773 | } 774 | return v 775 | } 776 | 777 | // String returns a single string from a selector. It is only allowed when selecting one field. 778 | func (us *UserSelect) String(ctx context.Context) (_ string, err error) { 779 | var v []string 780 | if v, err = us.Strings(ctx); err != nil { 781 | return 782 | } 783 | switch len(v) { 784 | case 1: 785 | return v[0], nil 786 | case 0: 787 | err = &NotFoundError{user.Label} 788 | default: 789 | err = fmt.Errorf("ent: UserSelect.Strings returned %d results when one was expected", len(v)) 790 | } 791 | return 792 | } 793 | 794 | // StringX is like String, but panics if an error occurs. 795 | func (us *UserSelect) StringX(ctx context.Context) string { 796 | v, err := us.String(ctx) 797 | if err != nil { 798 | panic(err) 799 | } 800 | return v 801 | } 802 | 803 | // Ints returns list of ints from a selector. It is only allowed when selecting one field. 804 | func (us *UserSelect) Ints(ctx context.Context) ([]int, error) { 805 | if len(us.fields) > 1 { 806 | return nil, errors.New("ent: UserSelect.Ints is not achievable when selecting more than 1 field") 807 | } 808 | var v []int 809 | if err := us.Scan(ctx, &v); err != nil { 810 | return nil, err 811 | } 812 | return v, nil 813 | } 814 | 815 | // IntsX is like Ints, but panics if an error occurs. 816 | func (us *UserSelect) IntsX(ctx context.Context) []int { 817 | v, err := us.Ints(ctx) 818 | if err != nil { 819 | panic(err) 820 | } 821 | return v 822 | } 823 | 824 | // Int returns a single int from a selector. It is only allowed when selecting one field. 825 | func (us *UserSelect) Int(ctx context.Context) (_ int, err error) { 826 | var v []int 827 | if v, err = us.Ints(ctx); err != nil { 828 | return 829 | } 830 | switch len(v) { 831 | case 1: 832 | return v[0], nil 833 | case 0: 834 | err = &NotFoundError{user.Label} 835 | default: 836 | err = fmt.Errorf("ent: UserSelect.Ints returned %d results when one was expected", len(v)) 837 | } 838 | return 839 | } 840 | 841 | // IntX is like Int, but panics if an error occurs. 842 | func (us *UserSelect) IntX(ctx context.Context) int { 843 | v, err := us.Int(ctx) 844 | if err != nil { 845 | panic(err) 846 | } 847 | return v 848 | } 849 | 850 | // Float64s returns list of float64s from a selector. It is only allowed when selecting one field. 851 | func (us *UserSelect) Float64s(ctx context.Context) ([]float64, error) { 852 | if len(us.fields) > 1 { 853 | return nil, errors.New("ent: UserSelect.Float64s is not achievable when selecting more than 1 field") 854 | } 855 | var v []float64 856 | if err := us.Scan(ctx, &v); err != nil { 857 | return nil, err 858 | } 859 | return v, nil 860 | } 861 | 862 | // Float64sX is like Float64s, but panics if an error occurs. 863 | func (us *UserSelect) Float64sX(ctx context.Context) []float64 { 864 | v, err := us.Float64s(ctx) 865 | if err != nil { 866 | panic(err) 867 | } 868 | return v 869 | } 870 | 871 | // Float64 returns a single float64 from a selector. It is only allowed when selecting one field. 872 | func (us *UserSelect) Float64(ctx context.Context) (_ float64, err error) { 873 | var v []float64 874 | if v, err = us.Float64s(ctx); err != nil { 875 | return 876 | } 877 | switch len(v) { 878 | case 1: 879 | return v[0], nil 880 | case 0: 881 | err = &NotFoundError{user.Label} 882 | default: 883 | err = fmt.Errorf("ent: UserSelect.Float64s returned %d results when one was expected", len(v)) 884 | } 885 | return 886 | } 887 | 888 | // Float64X is like Float64, but panics if an error occurs. 889 | func (us *UserSelect) Float64X(ctx context.Context) float64 { 890 | v, err := us.Float64(ctx) 891 | if err != nil { 892 | panic(err) 893 | } 894 | return v 895 | } 896 | 897 | // Bools returns list of bools from a selector. It is only allowed when selecting one field. 898 | func (us *UserSelect) Bools(ctx context.Context) ([]bool, error) { 899 | if len(us.fields) > 1 { 900 | return nil, errors.New("ent: UserSelect.Bools is not achievable when selecting more than 1 field") 901 | } 902 | var v []bool 903 | if err := us.Scan(ctx, &v); err != nil { 904 | return nil, err 905 | } 906 | return v, nil 907 | } 908 | 909 | // BoolsX is like Bools, but panics if an error occurs. 910 | func (us *UserSelect) BoolsX(ctx context.Context) []bool { 911 | v, err := us.Bools(ctx) 912 | if err != nil { 913 | panic(err) 914 | } 915 | return v 916 | } 917 | 918 | // Bool returns a single bool from a selector. It is only allowed when selecting one field. 919 | func (us *UserSelect) Bool(ctx context.Context) (_ bool, err error) { 920 | var v []bool 921 | if v, err = us.Bools(ctx); err != nil { 922 | return 923 | } 924 | switch len(v) { 925 | case 1: 926 | return v[0], nil 927 | case 0: 928 | err = &NotFoundError{user.Label} 929 | default: 930 | err = fmt.Errorf("ent: UserSelect.Bools returned %d results when one was expected", len(v)) 931 | } 932 | return 933 | } 934 | 935 | // BoolX is like Bool, but panics if an error occurs. 936 | func (us *UserSelect) BoolX(ctx context.Context) bool { 937 | v, err := us.Bool(ctx) 938 | if err != nil { 939 | panic(err) 940 | } 941 | return v 942 | } 943 | 944 | func (us *UserSelect) sqlScan(ctx context.Context, v interface{}) error { 945 | rows := &sql.Rows{} 946 | query, args := us.sql.Query() 947 | if err := us.driver.Query(ctx, query, args, rows); err != nil { 948 | return err 949 | } 950 | defer rows.Close() 951 | return sql.ScanSlice(rows, v) 952 | } 953 | -------------------------------------------------------------------------------- /ent/user_update.go: -------------------------------------------------------------------------------- 1 | // Code generated by entc, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "time" 9 | 10 | "entgo.io/ent/dialect/sql" 11 | "entgo.io/ent/dialect/sql/sqlgraph" 12 | "entgo.io/ent/schema/field" 13 | "github.com/fitv/min/ent/predicate" 14 | "github.com/fitv/min/ent/user" 15 | ) 16 | 17 | // UserUpdate is the builder for updating User entities. 18 | type UserUpdate struct { 19 | config 20 | hooks []Hook 21 | mutation *UserMutation 22 | } 23 | 24 | // Where appends a list predicates to the UserUpdate builder. 25 | func (uu *UserUpdate) Where(ps ...predicate.User) *UserUpdate { 26 | uu.mutation.Where(ps...) 27 | return uu 28 | } 29 | 30 | // SetUpdatedAt sets the "updated_at" field. 31 | func (uu *UserUpdate) SetUpdatedAt(t time.Time) *UserUpdate { 32 | uu.mutation.SetUpdatedAt(t) 33 | return uu 34 | } 35 | 36 | // SetUsername sets the "username" field. 37 | func (uu *UserUpdate) SetUsername(s string) *UserUpdate { 38 | uu.mutation.SetUsername(s) 39 | return uu 40 | } 41 | 42 | // SetPassword sets the "password" field. 43 | func (uu *UserUpdate) SetPassword(s string) *UserUpdate { 44 | uu.mutation.SetPassword(s) 45 | return uu 46 | } 47 | 48 | // Mutation returns the UserMutation object of the builder. 49 | func (uu *UserUpdate) Mutation() *UserMutation { 50 | return uu.mutation 51 | } 52 | 53 | // Save executes the query and returns the number of nodes affected by the update operation. 54 | func (uu *UserUpdate) Save(ctx context.Context) (int, error) { 55 | var ( 56 | err error 57 | affected int 58 | ) 59 | if err := uu.defaults(); err != nil { 60 | return 0, err 61 | } 62 | if len(uu.hooks) == 0 { 63 | affected, err = uu.sqlSave(ctx) 64 | } else { 65 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 66 | mutation, ok := m.(*UserMutation) 67 | if !ok { 68 | return nil, fmt.Errorf("unexpected mutation type %T", m) 69 | } 70 | uu.mutation = mutation 71 | affected, err = uu.sqlSave(ctx) 72 | mutation.done = true 73 | return affected, err 74 | }) 75 | for i := len(uu.hooks) - 1; i >= 0; i-- { 76 | if uu.hooks[i] == nil { 77 | return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") 78 | } 79 | mut = uu.hooks[i](mut) 80 | } 81 | if _, err := mut.Mutate(ctx, uu.mutation); err != nil { 82 | return 0, err 83 | } 84 | } 85 | return affected, err 86 | } 87 | 88 | // SaveX is like Save, but panics if an error occurs. 89 | func (uu *UserUpdate) SaveX(ctx context.Context) int { 90 | affected, err := uu.Save(ctx) 91 | if err != nil { 92 | panic(err) 93 | } 94 | return affected 95 | } 96 | 97 | // Exec executes the query. 98 | func (uu *UserUpdate) Exec(ctx context.Context) error { 99 | _, err := uu.Save(ctx) 100 | return err 101 | } 102 | 103 | // ExecX is like Exec, but panics if an error occurs. 104 | func (uu *UserUpdate) ExecX(ctx context.Context) { 105 | if err := uu.Exec(ctx); err != nil { 106 | panic(err) 107 | } 108 | } 109 | 110 | // defaults sets the default values of the builder before save. 111 | func (uu *UserUpdate) defaults() error { 112 | if _, ok := uu.mutation.UpdatedAt(); !ok { 113 | if user.UpdateDefaultUpdatedAt == nil { 114 | return fmt.Errorf("ent: uninitialized user.UpdateDefaultUpdatedAt (forgotten import ent/runtime?)") 115 | } 116 | v := user.UpdateDefaultUpdatedAt() 117 | uu.mutation.SetUpdatedAt(v) 118 | } 119 | return nil 120 | } 121 | 122 | func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { 123 | _spec := &sqlgraph.UpdateSpec{ 124 | Node: &sqlgraph.NodeSpec{ 125 | Table: user.Table, 126 | Columns: user.Columns, 127 | ID: &sqlgraph.FieldSpec{ 128 | Type: field.TypeInt, 129 | Column: user.FieldID, 130 | }, 131 | }, 132 | } 133 | if ps := uu.mutation.predicates; len(ps) > 0 { 134 | _spec.Predicate = func(selector *sql.Selector) { 135 | for i := range ps { 136 | ps[i](selector) 137 | } 138 | } 139 | } 140 | if value, ok := uu.mutation.UpdatedAt(); ok { 141 | _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ 142 | Type: field.TypeTime, 143 | Value: value, 144 | Column: user.FieldUpdatedAt, 145 | }) 146 | } 147 | if value, ok := uu.mutation.Username(); ok { 148 | _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ 149 | Type: field.TypeString, 150 | Value: value, 151 | Column: user.FieldUsername, 152 | }) 153 | } 154 | if value, ok := uu.mutation.Password(); ok { 155 | _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ 156 | Type: field.TypeString, 157 | Value: value, 158 | Column: user.FieldPassword, 159 | }) 160 | } 161 | if n, err = sqlgraph.UpdateNodes(ctx, uu.driver, _spec); err != nil { 162 | if _, ok := err.(*sqlgraph.NotFoundError); ok { 163 | err = &NotFoundError{user.Label} 164 | } else if sqlgraph.IsConstraintError(err) { 165 | err = &ConstraintError{err.Error(), err} 166 | } 167 | return 0, err 168 | } 169 | return n, nil 170 | } 171 | 172 | // UserUpdateOne is the builder for updating a single User entity. 173 | type UserUpdateOne struct { 174 | config 175 | fields []string 176 | hooks []Hook 177 | mutation *UserMutation 178 | } 179 | 180 | // SetUpdatedAt sets the "updated_at" field. 181 | func (uuo *UserUpdateOne) SetUpdatedAt(t time.Time) *UserUpdateOne { 182 | uuo.mutation.SetUpdatedAt(t) 183 | return uuo 184 | } 185 | 186 | // SetUsername sets the "username" field. 187 | func (uuo *UserUpdateOne) SetUsername(s string) *UserUpdateOne { 188 | uuo.mutation.SetUsername(s) 189 | return uuo 190 | } 191 | 192 | // SetPassword sets the "password" field. 193 | func (uuo *UserUpdateOne) SetPassword(s string) *UserUpdateOne { 194 | uuo.mutation.SetPassword(s) 195 | return uuo 196 | } 197 | 198 | // Mutation returns the UserMutation object of the builder. 199 | func (uuo *UserUpdateOne) Mutation() *UserMutation { 200 | return uuo.mutation 201 | } 202 | 203 | // Select allows selecting one or more fields (columns) of the returned entity. 204 | // The default is selecting all fields defined in the entity schema. 205 | func (uuo *UserUpdateOne) Select(field string, fields ...string) *UserUpdateOne { 206 | uuo.fields = append([]string{field}, fields...) 207 | return uuo 208 | } 209 | 210 | // Save executes the query and returns the updated User entity. 211 | func (uuo *UserUpdateOne) Save(ctx context.Context) (*User, error) { 212 | var ( 213 | err error 214 | node *User 215 | ) 216 | if err := uuo.defaults(); err != nil { 217 | return nil, err 218 | } 219 | if len(uuo.hooks) == 0 { 220 | node, err = uuo.sqlSave(ctx) 221 | } else { 222 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 223 | mutation, ok := m.(*UserMutation) 224 | if !ok { 225 | return nil, fmt.Errorf("unexpected mutation type %T", m) 226 | } 227 | uuo.mutation = mutation 228 | node, err = uuo.sqlSave(ctx) 229 | mutation.done = true 230 | return node, err 231 | }) 232 | for i := len(uuo.hooks) - 1; i >= 0; i-- { 233 | if uuo.hooks[i] == nil { 234 | return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") 235 | } 236 | mut = uuo.hooks[i](mut) 237 | } 238 | if _, err := mut.Mutate(ctx, uuo.mutation); err != nil { 239 | return nil, err 240 | } 241 | } 242 | return node, err 243 | } 244 | 245 | // SaveX is like Save, but panics if an error occurs. 246 | func (uuo *UserUpdateOne) SaveX(ctx context.Context) *User { 247 | node, err := uuo.Save(ctx) 248 | if err != nil { 249 | panic(err) 250 | } 251 | return node 252 | } 253 | 254 | // Exec executes the query on the entity. 255 | func (uuo *UserUpdateOne) Exec(ctx context.Context) error { 256 | _, err := uuo.Save(ctx) 257 | return err 258 | } 259 | 260 | // ExecX is like Exec, but panics if an error occurs. 261 | func (uuo *UserUpdateOne) ExecX(ctx context.Context) { 262 | if err := uuo.Exec(ctx); err != nil { 263 | panic(err) 264 | } 265 | } 266 | 267 | // defaults sets the default values of the builder before save. 268 | func (uuo *UserUpdateOne) defaults() error { 269 | if _, ok := uuo.mutation.UpdatedAt(); !ok { 270 | if user.UpdateDefaultUpdatedAt == nil { 271 | return fmt.Errorf("ent: uninitialized user.UpdateDefaultUpdatedAt (forgotten import ent/runtime?)") 272 | } 273 | v := user.UpdateDefaultUpdatedAt() 274 | uuo.mutation.SetUpdatedAt(v) 275 | } 276 | return nil 277 | } 278 | 279 | func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) { 280 | _spec := &sqlgraph.UpdateSpec{ 281 | Node: &sqlgraph.NodeSpec{ 282 | Table: user.Table, 283 | Columns: user.Columns, 284 | ID: &sqlgraph.FieldSpec{ 285 | Type: field.TypeInt, 286 | Column: user.FieldID, 287 | }, 288 | }, 289 | } 290 | id, ok := uuo.mutation.ID() 291 | if !ok { 292 | return nil, &ValidationError{Name: "ID", err: fmt.Errorf("missing User.ID for update")} 293 | } 294 | _spec.Node.ID.Value = id 295 | if fields := uuo.fields; len(fields) > 0 { 296 | _spec.Node.Columns = make([]string, 0, len(fields)) 297 | _spec.Node.Columns = append(_spec.Node.Columns, user.FieldID) 298 | for _, f := range fields { 299 | if !user.ValidColumn(f) { 300 | return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} 301 | } 302 | if f != user.FieldID { 303 | _spec.Node.Columns = append(_spec.Node.Columns, f) 304 | } 305 | } 306 | } 307 | if ps := uuo.mutation.predicates; len(ps) > 0 { 308 | _spec.Predicate = func(selector *sql.Selector) { 309 | for i := range ps { 310 | ps[i](selector) 311 | } 312 | } 313 | } 314 | if value, ok := uuo.mutation.UpdatedAt(); ok { 315 | _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ 316 | Type: field.TypeTime, 317 | Value: value, 318 | Column: user.FieldUpdatedAt, 319 | }) 320 | } 321 | if value, ok := uuo.mutation.Username(); ok { 322 | _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ 323 | Type: field.TypeString, 324 | Value: value, 325 | Column: user.FieldUsername, 326 | }) 327 | } 328 | if value, ok := uuo.mutation.Password(); ok { 329 | _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ 330 | Type: field.TypeString, 331 | Value: value, 332 | Column: user.FieldPassword, 333 | }) 334 | } 335 | _node = &User{config: uuo.config} 336 | _spec.Assign = _node.assignValues 337 | _spec.ScanValues = _node.scanValues 338 | if err = sqlgraph.UpdateNode(ctx, uuo.driver, _spec); err != nil { 339 | if _, ok := err.(*sqlgraph.NotFoundError); ok { 340 | err = &NotFoundError{user.Label} 341 | } else if sqlgraph.IsConstraintError(err) { 342 | err = &ConstraintError{err.Error(), err} 343 | } 344 | return nil, err 345 | } 346 | return _node, nil 347 | } 348 | -------------------------------------------------------------------------------- /global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "embed" 5 | 6 | "github.com/fitv/go-i18n" 7 | "github.com/fitv/go-logger" 8 | "github.com/fitv/min/core/app" 9 | "github.com/fitv/min/core/cache" 10 | "github.com/fitv/min/core/db" 11 | "github.com/fitv/min/ent" 12 | ut "github.com/go-playground/universal-translator" 13 | "github.com/go-redis/redis/v8" 14 | ) 15 | 16 | var App *app.Application 17 | 18 | func FS() embed.FS { 19 | return App.FS 20 | } 21 | 22 | func Ent() *ent.Client { 23 | return App.DB.Client() 24 | } 25 | 26 | func DB() *db.DB { 27 | return App.DB 28 | } 29 | 30 | func Cache() cache.Cache { 31 | return App.Cache 32 | } 33 | 34 | func Redis() *redis.Client { 35 | return App.Redis.Client() 36 | } 37 | 38 | func Log() *logger.Logger { 39 | return App.Logger 40 | } 41 | 42 | func Lang() *i18n.I18n { 43 | return App.Lang 44 | } 45 | 46 | func Trans() ut.Translator { 47 | return App.Translator 48 | } 49 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fitv/min 2 | 3 | go 1.17 4 | 5 | require ( 6 | entgo.io/ent v0.11.1 7 | github.com/fitv/go-i18n v1.0.4 8 | github.com/fitv/go-logger v1.0.4 9 | github.com/gin-gonic/gin v1.8.1 10 | github.com/go-playground/locales v0.14.0 11 | github.com/go-playground/universal-translator v0.18.0 12 | github.com/go-playground/validator/v10 v10.11.0 13 | github.com/go-redis/redis/v8 v8.11.5 14 | github.com/go-sql-driver/mysql v1.6.0 15 | github.com/golang-jwt/jwt/v4 v4.4.2 16 | github.com/google/uuid v1.3.0 17 | github.com/spf13/cast v1.5.0 18 | github.com/spf13/viper v1.12.0 19 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa 20 | ) 21 | 22 | require ( 23 | ariga.io/atlas v0.5.0 // indirect 24 | github.com/agext/levenshtein v1.2.3 // indirect 25 | github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect 26 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 27 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 28 | github.com/fsnotify/fsnotify v1.5.4 // indirect 29 | github.com/gin-contrib/sse v0.1.0 // indirect 30 | github.com/go-openapi/inflect v0.19.0 // indirect 31 | github.com/goccy/go-json v0.9.10 // indirect 32 | github.com/google/go-cmp v0.5.8 // indirect 33 | github.com/hashicorp/hcl v1.0.0 // indirect 34 | github.com/hashicorp/hcl/v2 v2.13.0 // indirect 35 | github.com/json-iterator/go v1.1.12 // indirect 36 | github.com/leodido/go-urn v1.2.1 // indirect 37 | github.com/magiconair/properties v1.8.6 // indirect 38 | github.com/mattn/go-isatty v0.0.14 // indirect 39 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 40 | github.com/mitchellh/mapstructure v1.5.0 // indirect 41 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 42 | github.com/modern-go/reflect2 v1.0.2 // indirect 43 | github.com/pelletier/go-toml v1.9.5 // indirect 44 | github.com/pelletier/go-toml/v2 v2.0.2 // indirect 45 | github.com/spf13/afero v1.9.2 // indirect 46 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 47 | github.com/spf13/pflag v1.0.5 // indirect 48 | github.com/subosito/gotenv v1.4.0 // indirect 49 | github.com/ugorji/go/codec v1.2.7 // indirect 50 | github.com/zclconf/go-cty v1.10.0 // indirect 51 | golang.org/x/mod v0.5.1 // indirect 52 | golang.org/x/net v0.0.0-20220805013720-a33c5aa5df48 // indirect 53 | golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 // indirect 54 | golang.org/x/text v0.3.7 // indirect 55 | google.golang.org/protobuf v1.28.1 // indirect 56 | gopkg.in/ini.v1 v1.66.6 // indirect 57 | gopkg.in/yaml.v2 v2.4.0 // indirect 58 | gopkg.in/yaml.v3 v3.0.1 // indirect 59 | ) 60 | -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | auth: 2 | invalid_credentials: invalid credentials 3 | 4 | message: 5 | success: success 6 | failed: failed 7 | not_found: not found 8 | unauthorized: unauthorized 9 | forbidden: forbidden 10 | server_error: server error 11 | validate_failed: verification failed 12 | 13 | user: 14 | not_found: user not exists 15 | username: username 16 | password: password 17 | 18 | validation: 19 | mobile: "{0} format is incorrect" 20 | -------------------------------------------------------------------------------- /locales/zh.yml: -------------------------------------------------------------------------------- 1 | auth: 2 | invalid_credentials: 用户名或密码错误 3 | 4 | message: 5 | success: 操作成功 6 | failed: 操作失败 7 | not_found: 未找到 8 | unauthorized: 未登录 9 | forbidden: 无权限 10 | server_error: 服务器错误 11 | validate_failed: 验证失败 12 | 13 | user: 14 | not_found: 用户不存在 15 | username: 姓名 16 | password: 密码 17 | 18 | validation: 19 | mobile: "{0}格式不正确" 20 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "embed" 5 | 6 | "github.com/fitv/min/core/app" 7 | "github.com/fitv/min/core/service" 8 | _ "github.com/fitv/min/ent/runtime" 9 | "github.com/fitv/min/global" 10 | ) 11 | 12 | //go:embed static templates locales 13 | var fs embed.FS 14 | 15 | func main() { 16 | global.App = app.NewApplication(fs) 17 | defer global.App.Shutdown() 18 | 19 | global.App.AddService( 20 | &service.Logger{}, 21 | &service.Cache{}, 22 | &service.Redis{}, 23 | &service.Database{}, 24 | &service.Validator{}, 25 | &service.Translator{}, 26 | &service.Gin{}, 27 | &service.Router{}, 28 | ) 29 | 30 | global.App.Run() 31 | } 32 | -------------------------------------------------------------------------------- /routes/api.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | v1 "github.com/fitv/min/app/api/v1" 5 | "github.com/fitv/min/app/middleware" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func Api(c *gin.Engine) { 10 | auth := &v1.Auth{} 11 | migrate := &v1.Migrate{} 12 | 13 | v1 := c.Group("/api/v1", middleware.Cors()) 14 | { 15 | v1.GET("/migrate", migrate.Index) 16 | 17 | v1.POST("/auth/login", auth.Login) 18 | v1.POST("/auth/register", auth.Register) 19 | 20 | authorized := v1.Group("/", middleware.Auth()) 21 | { 22 | authorized.GET("/auth/profile", auth.Profile) 23 | authorized.POST("/auth/refresh", auth.Refresh) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /routes/web.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "html/template" 5 | "io/fs" 6 | "net/http" 7 | 8 | "github.com/fitv/min/app/middleware" 9 | "github.com/fitv/min/app/web" 10 | "github.com/fitv/min/global" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func Web(c *gin.Engine) { 15 | staticFS, _ := fs.Sub(global.FS(), "static") 16 | favicon, _ := global.FS().ReadFile("static/favicon.ico") 17 | c.SetHTMLTemplate(template.Must(template.ParseFS(global.FS(), "templates/*"))) 18 | 19 | home := web.Home{} 20 | apidoc := web.ApiDoc{} 21 | 22 | c.GET("/", home.Index) 23 | 24 | c.GET("/apidoc", apidoc.Index) 25 | 26 | static := c.Group("/", middleware.Static()) 27 | { 28 | static.StaticFS("/static", http.FS(staticFS)) 29 | 30 | static.GET("/favicon.ico", func(c *gin.Context) { 31 | c.Data(http.StatusOK, "image/x-icon", favicon) 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /static/apidoc.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | 3 | info: 4 | version: 1.0.0 5 | title: MIN 6 | 7 | servers: 8 | - url: http://127.0.0.1:3000/api/v1 9 | description: Local 10 | 11 | tags: 12 | - name: auth 13 | description: Authorization 14 | 15 | paths: 16 | /auth/login: 17 | post: 18 | summary: Login 19 | security: [] 20 | tags: 21 | - auth 22 | requestBody: 23 | content: 24 | application/json: 25 | schema: 26 | type: object 27 | properties: 28 | username: 29 | type: string 30 | description: Username 31 | password: 32 | type: string 33 | description: Password 34 | responses: 35 | "200": 36 | description: Success 37 | content: 38 | application/json: 39 | schema: 40 | $ref: "#/components/schemas/TokenObject" 41 | "400": 42 | description: Failed 43 | content: 44 | application/json: 45 | schema: 46 | $ref: "#/components/schemas/ErrorObject" 47 | "422": 48 | description: Validation Error 49 | content: 50 | application/json: 51 | schema: 52 | $ref: "#/components/schemas/FormErrorObject" 53 | 54 | /auth/register: 55 | post: 56 | summary: Register 57 | security: [] 58 | tags: 59 | - auth 60 | requestBody: 61 | content: 62 | application/json: 63 | schema: 64 | type: object 65 | properties: 66 | username: 67 | type: string 68 | description: Username 69 | password: 70 | type: string 71 | description: Password 72 | responses: 73 | "200": 74 | description: Success 75 | content: 76 | application/json: 77 | schema: 78 | $ref: "#/components/schemas/UserObject" 79 | "400": 80 | description: Failed 81 | content: 82 | application/json: 83 | schema: 84 | $ref: "#/components/schemas/ErrorObject" 85 | "422": 86 | description: Validation Error 87 | content: 88 | application/json: 89 | schema: 90 | $ref: "#/components/schemas/FormErrorObject" 91 | 92 | /auth/refresh: 93 | post: 94 | summary: Refresh Token 95 | tags: 96 | - auth 97 | responses: 98 | "200": 99 | description: Success 100 | content: 101 | application/json: 102 | schema: 103 | $ref: "#/components/schemas/TokenObject" 104 | "400": 105 | description: Failed 106 | content: 107 | application/json: 108 | schema: 109 | $ref: "#/components/schemas/ErrorObject" 110 | 111 | /auth/profile: 112 | get: 113 | summary: User Profile 114 | tags: 115 | - auth 116 | responses: 117 | "200": 118 | description: Success 119 | content: 120 | application/json: 121 | schema: 122 | allOf: 123 | - $ref: "#/components/schemas/UserObject" 124 | components: 125 | securitySchemes: 126 | bearerAuth: 127 | type: http 128 | scheme: bearer 129 | bearerFormat: JWT 130 | 131 | schemas: 132 | UserObject: 133 | description: User Object 134 | type: object 135 | properties: 136 | id: 137 | type: integer 138 | description: ID 139 | username: 140 | type: string 141 | description: Username 142 | 143 | TokenObject: 144 | description: Token Object 145 | type: object 146 | properties: 147 | access_token: 148 | type: string 149 | description: token 150 | token_type: 151 | type: string 152 | description: token type 153 | expires_in: 154 | type: integer 155 | description: Expiration(seconds) 156 | 157 | MessageObject: 158 | description: Message Object 159 | type: object 160 | properties: 161 | message: 162 | type: string 163 | 164 | ErrorObject: 165 | description: Error Object 166 | type: object 167 | properties: 168 | message: 169 | type: string 170 | 171 | FormErrorObject: 172 | description: Validation Error Object 173 | type: object 174 | properties: 175 | message: 176 | type: string 177 | errors: 178 | type: object 179 | properties: 180 | field_name: 181 | type: string 182 | 183 | security: 184 | - bearerAuth: [] 185 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitv/min/9528f6dac65022269891b2210974c00464c575dd/static/favicon.ico -------------------------------------------------------------------------------- /static/images/swagger-favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitv/min/9528f6dac65022269891b2210974c00464c575dd/static/images/swagger-favicon-16x16.png -------------------------------------------------------------------------------- /static/images/swagger-favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitv/min/9528f6dac65022269891b2210974c00464c575dd/static/images/swagger-favicon-32x32.png -------------------------------------------------------------------------------- /templates/404.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 404 - Not Found 5 | 6 | 7 |

Not Found

8 | 9 | 10 | -------------------------------------------------------------------------------- /templates/500.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 500 - Server Error 5 | 6 | 7 |

Server Error

8 | 9 | 10 | -------------------------------------------------------------------------------- /templates/apidoc.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Swagger UI 6 | 7 | 8 | 9 | 30 | 31 | 32 | 33 |
34 | 35 | 36 | 37 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /templates/home.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ .title }} 5 | 6 | 7 |

{{ .title }}

8 | 9 | 10 | -------------------------------------------------------------------------------- /util/file/file.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import "os" 4 | 5 | // Exist checks if a file or directory exists. 6 | func Exist(name string) bool { 7 | if _, err := os.Stat(name); os.IsNotExist(err) { 8 | return false 9 | } 10 | return true 11 | } 12 | 13 | // MkdirAll creates a directory path recursively. 14 | func MkdirAll(path string) error { 15 | if err := os.MkdirAll(path, 0755); err != nil { 16 | return err 17 | } 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /util/hash/hash.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/md5" 6 | "crypto/sha1" 7 | "crypto/sha256" 8 | "encoding/hex" 9 | "io/ioutil" 10 | 11 | "golang.org/x/crypto/bcrypt" 12 | ) 13 | 14 | // Make a bcrypt password from a plaintext password 15 | func Make(password string) ([]byte, error) { 16 | return bcrypt.GenerateFromPassword([]byte(password), 10) 17 | } 18 | 19 | // Check determines if a password matches a hash 20 | func Check(hash string, password string) bool { 21 | return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil 22 | } 23 | 24 | // Sha1 returns a sha1 hash of a string 25 | func Sha1(str string) string { 26 | hasher := sha1.New() 27 | hasher.Write([]byte(str)) 28 | return hex.EncodeToString(hasher.Sum(nil)) 29 | } 30 | 31 | // Md5 returns a md5 hash of a string 32 | func Md5(str string) string { 33 | hasher := md5.New() 34 | hasher.Write([]byte(str)) 35 | return hex.EncodeToString(hasher.Sum(nil)) 36 | } 37 | 38 | // HmacSha256 returns a hmac sha256 hash of a string 39 | func HmacSha256(str string, key string) string { 40 | hasher := hmac.New(sha256.New, []byte(key)) 41 | hasher.Write([]byte(str)) 42 | return hex.EncodeToString(hasher.Sum(nil)) 43 | } 44 | 45 | // FileMd5 returns a md5 hash of a file 46 | func FileMd5(path string) (string, error) { 47 | file, err := ioutil.ReadFile(path) 48 | if err != nil { 49 | return "", err 50 | } 51 | hasher := md5.New() 52 | hasher.Write([]byte(file)) 53 | return hex.EncodeToString(hasher.Sum(nil)), nil 54 | } 55 | -------------------------------------------------------------------------------- /util/str/str.go: -------------------------------------------------------------------------------- 1 | package str 2 | 3 | import ( 4 | "math/rand" 5 | "regexp" 6 | "strings" 7 | "time" 8 | "unicode/utf8" 9 | ) 10 | 11 | const ( 12 | pool = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 13 | ) 14 | 15 | var ( 16 | // regexCamel is a regular expression to match camel case. 17 | regexCamel = regexp.MustCompile("([a-z])([A-Z])") 18 | ) 19 | 20 | // Len returns the length of the utf8 string. 21 | func Len(str string) int { 22 | return utf8.RuneCountInString(str) 23 | } 24 | 25 | // Random returns a random string of the specified length. 26 | func Random(length int) string { 27 | letters := make([]byte, length) 28 | 29 | rand.Seed(time.Now().UnixNano()) 30 | for i := range letters { 31 | letters[i] = pool[rand.Intn(len(pool))] 32 | } 33 | return string(letters) 34 | } 35 | 36 | // ToSnakeCase converts a string to snake case. 37 | func ToSnakeCase(str string) string { 38 | return strings.ToLower(regexCamel.ReplaceAllString(str, "${1}_${2}")) 39 | } 40 | --------------------------------------------------------------------------------