diff --git a/backend/app/tenancy/tenancy.go b/backend/app/tenancy/tenancy.go deleted file mode 100644 index d4171c6..0000000 --- a/backend/app/tenancy/tenancy.go +++ /dev/null @@ -1,71 +0,0 @@ -package tenancy - -import ( - "database/sql" - "regexp" - "strings" - - "quyun/v2/app/errorx" - - "github.com/gofiber/fiber/v3" - "github.com/google/uuid" -) - -const ( - LocalTenantCode = "tenant_code" - LocalTenantID = "tenant_id" - LocalTenantUUID = "tenant_uuid" -) - -var tenantCodeRe = regexp.MustCompile(`^[a-z0-9_-]+$`) - -type Tenant struct { - ID int64 - Code string - UUID uuid.UUID -} - -func ResolveTenant(c fiber.Ctx, db *sql.DB) (*Tenant, error) { - raw := strings.TrimSpace(c.Params("tenant_code")) - code := strings.ToLower(raw) - if code == "" || !tenantCodeRe.MatchString(code) { - return nil, errorx.ErrInvalidParameter.WithMsg("invalid tenant_code") - } - - var ( - id int64 - tenantUUID uuid.UUID - status int16 - ) - err := db.QueryRowContext( - c.Context(), - `SELECT id, tenant_uuid, status FROM tenants WHERE lower(tenant_code) = $1 LIMIT 1`, - code, - ).Scan(&id, &tenantUUID, &status) - if err != nil { - if err == sql.ErrNoRows { - return nil, fiber.ErrNotFound - } - return nil, errorx.ErrDatabaseError.WithMsg("database error").WithParams(err.Error()) - } - - // status: 0 enabled (by default) - if status != 0 { - return nil, fiber.ErrNotFound - } - - return &Tenant{ID: id, Code: code, UUID: tenantUUID}, nil -} - -func Middleware(db *sql.DB) fiber.Handler { - return func(c fiber.Ctx) error { - tenant, err := ResolveTenant(c, db) - if err != nil { - return err - } - c.Locals(LocalTenantCode, tenant.Code) - c.Locals(LocalTenantID, tenant.ID) - c.Locals(LocalTenantUUID, tenant.UUID.String()) - return c.Next() - } -} diff --git a/backend/docs/dev/api.md b/backend/docs/dev/api.md deleted file mode 100644 index 7574e5d..0000000 --- a/backend/docs/dev/api.md +++ /dev/null @@ -1,138 +0,0 @@ -# Backend 新增 HTTP 接口流程(Go + Fiber + atomctl) - -本文档描述在本仓库 `backend/` 中新增一个 HTTP 接口(例如 `/super/v1/...`)的标准流程,包含路由生成、Swagger 文档生成、参数绑定与测试验证。 - -## 相关目录 - -- 路由与 Controller:`backend/app/http/**` - - Super 端示例:`backend/app/http/super/*.go` - - 生成路由:`backend/app/http/super/routes.gen.go`(自动生成,勿手改) -- Swagger 文档: - - `backend/docs/swagger.yaml` - - `backend/docs/swagger.json` - - `backend/docs/docs.go`(自动生成,勿手改) -- 本地接口调试示例:`backend/super.http` -- 命令:`backend/Makefile`(`make init`/`make run`/`make test` 等) - -## 增加一个新接口(推荐步骤) - -### 1) 选择模块与 BasePath - -- **Super 管理端**:一般挂在 `/super/v1/...`,代码放在 `backend/app/http/super/`。 -- **租户端**:项目 `main.go` 注解里有 `@BasePath /t/{tenant_code}/v1`,通常租户端接口会以该前缀为基础(具体以现有路由模块为准)。 - -先决定: - -- `METHOD`:GET/POST/PATCH/DELETE… -- `PATH`:例如 `/super/v1/users/{id}` 或 `/super/v1/users/:id` -- 鉴权:是否需要 token/权限(跟随模块现有中间件) -- 请求:path/query/body -- 响应:结构体 / 分页 / KV 列表等 - -### 2) 定义请求 DTO(Filter/Form)与响应 DTO - -Super 模块常见模式: - -- 列表分页:`dto.*Filter` / `dto.*PageFilter` + 返回 `requests.Pager` -- 更新类接口:`dto.*UpdateForm`(body) -- KV 枚举列表:返回 `[]requests.KV` - -可参考: - -- `backend/app/http/super/dto/*` -- `backend/app/http/super/user.go`、`backend/app/http/super/tenant.go` - -### 3) 编写 Controller 方法(带 Swagger 注解 + Bind) - -在对应模块的 `*.go` 中新增方法,保持和现有风格一致: - -- Controller struct 上保留 `// @provider` -- 方法上补齐 swagger 注解:`@Summary/@Tags/@Param/@Success/@Router` -- 使用 `@Bind` 约定参数来源(path/query/body) - -示例(参考 `backend/app/http/super/user.go`): - -```go -// @Summary 用户状态列表 -// @Tags Super -// @Accept json -// @Produce json -// @Success 200 {array} requests.KV -// @Router /super/v1/users/statuses [get] -func (*user) statusList(ctx fiber.Ctx) ([]requests.KV, error) { ... } -``` - -注意: - -- `@Router` 的路径写法通常与 Fiber 路由一致(例如 `:userID`)。 -- 参数绑定会驱动路由代码生成(见下一步)。 - -### 4) 连接业务层(Service) - -Controller 内尽量只做: - -- 参数解析/校验 -- 调用 `services.*` 完成业务 -- 返回结果或 error - -业务逻辑集中放在 `backend/app/services`(结合现有实现),涉及 DB 的部分走现有模型/仓储层(依项目既有组织)。 - -### 5) 生成路由代码(routes.gen.go) - -本项目路由是由 `atomctl` 自动生成的。 - -常用命令(在 `backend/` 下执行): - -- 仅生成路由:`atomctl gen route` -- 全量初始化/更新(含 swagger/enum/route/service 等):`make init` - -生成完成后检查: - -- `backend/app/http//routes.gen.go` 是否出现新路由 -- 路由 METHOD/PATH、参数绑定是否正确 - -### 6) 生成 Swagger 文档(swagger.yaml/json/docs.go) - -Swagger 也是由工具生成并落盘到 `backend/docs/`: - -- `atomctl swag init` -- 或直接 `make init`(会包含该步骤) - -生成完成后检查: - -- `backend/docs/swagger.yaml`、`backend/docs/swagger.json` 是否包含新接口 -- `backend/docs/docs.go` 是否同步更新 - -### 7) 本地验证 - -启动: - -- `make run`(会先 `make build`) - -验证方式: - -- 使用 `backend/super.http` 增加/执行请求 -- 或用 curl/Swagger UI(若项目已暴露 swagger 页面) - -### 8) 增加测试(建议) - -优先参考现有 e2e 测试: - -- `backend/tests/e2e/*` - -覆盖至少: - -- 正常请求返回 -- 参数缺失/非法 -- 权限不足/未登录(如该接口需要鉴权) - -运行: - -- `make test` - -## 常见注意事项 - -- 不要手改 `*.gen.go`、`backend/docs/docs.go`:它们由 `atomctl` 生成。 -- 确认查询参数命名与 swagger 一致(例如 `page/limit/asc/desc/status`),前端会按 swagger 拼 query。 -- 路由路径参数请在 `@Router` 与函数签名/`@Bind` 里保持一致(例如 `tenantID`、`userID`)。 - diff --git a/backend/docs/dev/http_api.md b/backend/docs/dev/http_api.md new file mode 100644 index 0000000..e6b8c42 --- /dev/null +++ b/backend/docs/dev/http_api.md @@ -0,0 +1,92 @@ +# 新增 HTTP 接口流程 + +项目 `controller` 定义于 `backend/app/http/[module_name]/[controller].go` 文件中。 每个 `controller` 负责处理一组相关的 HTTP 请求。 例如,`backend/app/http/super/tenant.go` 负责处理与租户相关的请求。 + +## 新增接口步骤 + +1. 在 `controller` 中新增方法以处理特定的 HTTP 请求。 例如,在 `tenant.go` 新增一个 `list` 方法来处理列出租户的请求。 +2. 定义相关 `swagger` 注解以生成 API 文档。 这些注解通常位于方法上方,描述了请求路径、参数和响应格式。 +3. 在模块 `dto/`(数据传输对象)目录中定义请求和响应的数据结构, 以确保数据的一致性和类型安全。 +4. 运行 `atomctl gen route` 生成路由文件,确保新接口被正确注册。 +5. 运行 `atomctl gen provider` 生成路由文件,确保新接口被正确注册。 + +## 接口定义示例: + +1. 实现需要返回数据的接口。 + +```go +func (*tenant) list(ctx fiber.Ctx, filter *dto.TenantFilter) (*requests.Pager, error) { + return nil,nil +} +``` + +2. 实现不需要返回数据的接口 + +```go +func (*tenant) update(ctx fiber.Ctx, tenantID int64, form *dto.TenantExpireUpdateForm) error { + return nil +} +``` + +## swagger 注解说明 + +- **@Summary**: 接口的简要描述。 +- **@Description**: 接口的详细描述。 +- **@Tags**: 接口所属的分类标签。 +- **@Accept**: 接口接受的数据格式。通常为 `json` +- **@Produce**: 接口返回的数据格式。通常为 `json` +- **@Param**: 定义接口的参数,包括参数名称、数据来源位置、数据类型、是否必须、和描述。如:`// @Param form body dto.LoginForm true "form"` +- **@Success**: 定义接口成功时的响应格式。如: + - 返回分页列表 `// @Success 200 {object} requests.Pager{items=dto.Item}` + - 直接返回对象 `// @Success 200 {object} dto.Item` + - 返回数据对象列表 `// @Success 200 {array} dto.Item` +- **@Router**: 定义接口的路由信息,包括路径和请求方法。如:`// @Router /super/tenants [get|post|put|delete|patch]`, 如果需要定义 path 参数,使用 `:paramName` 语法表示,如:`/super/tenants/:tenantID` +- **@Bind**: 定义参数绑定方式,格式: `@Bind [key()] [model(|[:])]` + - `paramName` 与方法参数名一致(大小写敏感) + - `position`:`path`、`query`、`body`、`header`、`cookie`、`local`、`file` + - 可选: + - `key()` 覆盖默认键名; + - `model()` 详见“模型绑定”。 + +### 参数绑定 + +- query:标量用 `QueryParam[T]("key")`,非标量用 `Query[T]("key")` +- path:标量用 `PathParam[T]("key")`,非标量用 `Path[T]("key")` + - 若使用 `model()`(仅在 path 有效),会按字段值查询并绑定为 `T`,详见下文 +- header:`Header[T]("key")` +- body:`Body[T]("key")` +- cookie:`string` 用 `CookieParam("key")`,其他用 `Cookie[T]("key")` +- file:`File[multipart.FileHeader]("key")` +- local:`Local[T]("key")` + +说明: + +- 标量类型集合:`string`、`int`、`int32`、`int64`、`float32`、`float64`、`bool` +- `key` 默认等于 `paramName`;设置 `key(...)` 后以其为准 +- `file` 使用固定类型 `multipart.FileHeader` + +### 类型与指针处理 + +- 支持 `T`、`*T`、`pkg.T`、`*pkg.T`;会正确收集选择子表达式对应 import +- 忽略结尾为 `Context` 或 `Ctx` 的参数(框架上下文) +- 指针处理:除 `local` 外会去掉前导 `*` 作为泛型实参;`local` 保留指针(便于写回) + +### 模型绑定 + +当 `@Bind ... model(...)` 配合 `position=path` 使用时,将根据路径参数值查询模型并绑定为方法参数类型的实例(`T` 来自方法参数)。 + +- 语法: + - 仅字段:`model(id)`(推荐) + - 指定字段与类型:`model(id:int)`、`model(code:string)`(用于非字符串路径参数) + - 指定类型与字段:`model(pkg.Type:field)` 或 `model(pkg.Type)`(字段缺省为 `id`) +- 行为: + - 生成的绑定器会按给定字段构造查询条件并返回首条记录 + - 自动注入 import:`field "go.ipao.vip/gen/field"`,用于构造字段条件表达式 + +示例: + +```go +// @Router /users/:id [get] +// @Bind user path key(id) model(id) +func (uc *UserController) Show(ctx context.Context, user *models.User) (*UserDTO, error) +``` diff --git a/backend/docs/dev/model.md b/backend/docs/dev/model.md new file mode 100644 index 0000000..96ebfe8 --- /dev/null +++ b/backend/docs/dev/model.md @@ -0,0 +1,226 @@ +# 新增 model 流程 + +项目 `models` 定义于 `backend/database/models` 文件中。 每个 `model` 对应数据库中的一张表。 新增 `model` 的步骤如下: + +## 步骤 + +1. 运行 `atomctl migrate create [alter|create_table]` 创建迁移文件。 +2. 编辑生成的迁移文件,定义数据库表结构变更。不需要声明 `BEGIN` 和 `COMMIT`,框架会自动处理。table 名称使用复数形式,例如 `tenants`。 +3. 执行 `atomctl migrate up` 应用迁移,更新数据库结构。 +4. 对于 `JSON` `ARRAY` `ENUM` 等复杂字段类型,编辑 `database/.transform.yaml` 文件,在 `field_type.[table_name]` 定义字段与 Go 类型的映射关系。支持定义的数据类型参考数据类型章节 +5. 运行 `atomctl gen model` 生成或更新 `models` 代码,确保代码与数据库结构同步。 + +## 数据类型 + +### Enum 类型 + +不使用数据库原生 `ENUM` 类型,使用业务代码来声明枚举类型,步骤如下: + +1. 在 `pkg/consts/[table].go` 文件中,定义枚举字段的 Go 类型。例如: + +```go +// swagger:enum UserStatus +// ENUM(pending_verify, verified, banned, ) +type UserStatus string +``` + +2. 执行 `atomctl gen enum`,生成 `pkg/consts/[table].gen.go` + +### 其它支持的数据类型 + +`database/.transform.yaml` 中 `field_type` 支持将表字段映射为 `go.ipao.vip/gen/types` 提供的 PostgreSQL 扩展类型(在 `.transform.yaml` 的 `imports` 中引入 `go.ipao.vip/gen` 后,通常可直接使用 `types.*`)。 + +常用类型清单(对应 `gen/types/`): + +- `types.JSON`:`json/jsonb`(建议列类型用 `jsonb`) +- `types.JSONMap`:`json/jsonb` 的 `map[string]any` 形态 +- `types.JSONType[T]` / `types.JSONSlice[T]`:强类型 JSON(读写用,不提供 JSON 路径查询能力) +- `types.Array[T]`:PostgreSQL 数组(如 `text[]/int[]` 等) +- `types.UUID` / `types.BinUUID`:`uuid`(`BinUUID` 主要用于二进制存储场景) +- `types.Date` / `types.Time`:`date` / `time` +- `types.Money`:`money` +- `types.URL`:URL(通常落库为 `text/varchar`,由类型负责解析/序列化) +- `types.XML`:`xml` +- `types.HexBytes`:`bytea`(hex 表示) +- `types.BitString`:`bit/varbit` +- 网络类型:`types.Inet`(`inet`)、`types.CIDR`(`cidr`)、`types.MACAddr`(`macaddr`) +- 范围类型: + - `types.Int4Range`(`int4range`) + - `types.Int8Range`(`int8range`) + - `types.NumRange`(`numrange`) + - `types.TsRange`(`tsrange`) + - `types.TstzRange`(`tstzrange`) + - `types.DateRange`(`daterange`) +- 几何类型:`types.Point` / `types.Polygon` / `types.Box` / `types.Circle` / `types.Path` +- 全文检索:`types.TSQuery` / `types.TSVector` +- 可空类型:`types.Null[T]` 以及别名 `types.NullString/NullInt64/...`(需要字段允许 NULL) + +示例(`database/.transform.yaml`): + +```yaml +imports: + - go.ipao.vip/gen + - quyun/v2/pkg/consts +field_type: + users: + roles: types.Array[consts.Role] + meta: types.JSON + home_ip: types.Inet + profile: types.JSONType[Profile] + tenants: + uuid: types.UUID +``` + +### 关联关系字段说明(对齐 GORM) + +支持在 `database/.transform.yaml` 中为模型定义关联关系字段,生成对应的 GORM 关系标签。 +示例: + +```yaml +field_relate: + students: + Class: + # belong_to, has_one, has_many, many_to_many + relation: belongs_to + table: classes + references: id # 关联表的主键/被引用键(通常是 id) + foreign_key: class_id # 当前表上的外键列(如 students.class_id) + json: class + Teachers: + # belong_to, has_one, has_many, many_to_many + relation: many_to_many + table: teachers + pivot: class_teacher + foreign_key: class_id # 当前表(students)用于关联的键(转为结构体字段名 ClassID) + join_foreign_key: class_id # 中间表中指向当前表的列(class_teacher.class_id) + references: id # 关联表(teachers)被引用的列(转为结构体字段名 ID) + join_references: teacher_id # 中间表中指向关联表的列(class_teacher.teacher_id) + json: teachers + teachers: + Classes: + relation: many_to_many + table: classes + pivot: class_teacher + classes: + Teachers: + relation: many_to_many + table: teachers + pivot: class_teacher +``` + +关联关系配置项如下: + +- relation + + - 取值:`belongs_to`、`has_one`、`has_many`、`many_to_many`。 + - 对应 GORM 的四种关系:Belongs To、Has One、Has Many、Many2Many。 + +- table + + - 关联的目标表名(即另一侧模型对应的表)。 + +- pivot(仅 many_to_many) + + - 多对多中间表名称,对应 GORM 标签 `many2many:`。 + +- foreign_key(按关系含义不同) + + - 对应 GORM 标签 `foreignKey:`。 + - belongs_to:当前表上的外键列(例如 `students.class_id`),会映射为当前模型上的字段(如 `ClassID`)。 + - has_one / has_many:外键在对端表上(例如 `credit_cards.user_id`)。配置时仍在当前表的配置块里填“外键列名”,生成时会正确落到 GORM 标签中。 + +- references(按关系含义不同) + + - 对应 GORM 标签 `references:`。 + - belongs_to:对端表被引用的列(一般是 `id`),映射为对端模型字段名(如 `ID`)。 + - has_one / has_many:被对端外键引用的当前模型列(一般是当前模型的 `ID` 字段)。 + +- join_foreign_key(仅 many_to_many) + + - 对应 GORM 标签 `joinForeignKey:`,指中间表里“指向当前模型”的列(如 `class_teacher.class_id`)。 + +- join_references(仅 many_to_many) + - 对应 GORM 标签 `joinReferences:`,指中间表里“指向关联模型”的列(如 `class_teacher.teacher_id`)。 + +说明:生成器会结合数据库的 NamingStrategy 将列名(如 `class_id`、`teacher_id`)转换为结构体字段名(如 `ClassID`、`TeacherID`),并据此写入正确的 GORM 标签。 + +### 与 GORM 标签的对应关系 + +- belongs_to 示例(students → classes) + + - YAML: + - `foreign_key: class_id` + - `references: id` + - 生成的模型字段(示意): + - `Class Class gorm:"foreignKey:ClassID;references:ID"` + +- has_many 示例(users → credit_cards) + + - YAML(在 `users` 下配置 `CreditCards` 关系): + - `relation: has_many` + - `table: credit_cards` + - `foreign_key: user_id` (对端表上的外键列) + - `references: id` (当前模型被引用的列) + - 生成的模型字段(示意): + - `CreditCards []CreditCard gorm:"foreignKey:UserID;references:ID"` + +- many_to_many 示例(students ⇄ teachers,经由 class_teacher) + - YAML(在 `students` 下配置 `Teachers` 关系): + - `relation: many_to_many` + - `table: teachers` + - `pivot: class_teacher` + - `foreign_key: class_id` + - `join_foreign_key: class_id` + - `references: id` + - `join_references: teacher_id` + - 生成的模型字段(示意): + - `Teachers []Teacher gorm:"many2many:class_teacher;foreignKey:ClassID;references:ID;joinForeignKey:ClassID;joinReferences:TeacherID"` + +提示:GORM 在 many2many 下允许省略部分键,生成器也支持“只给必要字段”。若不确定,建议显式全部写出,避免命名不一致导致推断失败。 + +## model 功能扩展 + +模型生成完后可以创建 `database/models/[table].go` 文件,添加自定义方法、GORM Hook 来扩展 model 的使用。例如: + +```go +func (m *User) ComparePassword(ctx context.Context, password string) bool { + err := bcrypt.CompareHashAndPassword([]byte(m.Password), []byte(password)) + return err == nil +} +``` + +## 生成模型的使用 + +模型通常在 service 中使用,service 定义于 `app/services`, 通常用于对一类功能进行封装,方便 controller 层调用,不需要与表进行一一对应。 示例: + +```go +package services + +import "context" + +// @provider +type test struct{ + app *app.Config +} + +func (t *test) Test(ctx context.Context) (string, error) { + return "Test", nil +} +``` + +struct 中可以定义一个多上需要注入的 provider 对象,示例中的 app 会自动注入对应的 app.Config 实例。 +service 文件创建完成后需要运行 `atomctl gen service` 和 `atomctl gen provider` 完成依赖对象注入。 +service 调用 model 示例: + +```go +func (t *test) FindByID(ctx context.Context, userID int64) (*models.User, error) { + tbl, query := models.UserQuery.QueryContext(ctx) + + model, err := query.Preload(tbl.OwnedTenant, tbl.Tenants).Where(tbl.ID.Eq(userID)).First() + if err != nil { + return nil, errors.Wrapf(err, "FindByID failed, %d", userID) + } + return model, nil +} +··· +``` diff --git a/backend/llm.txt b/backend/llm.txt new file mode 100644 index 0000000..6d69a38 --- /dev/null +++ b/backend/llm.txt @@ -0,0 +1,207 @@ +# Backend Dev Rules (HTTP API + Model) + +This file condenses `backend/docs/dev/http_api.md` + `backend/docs/dev/model.md` into a checklist/rule format for LLMs. + +--- + +## 0) Golden rules (DO / DO NOT) + +- DO follow existing module layout under `backend/app/http//`. +- DO keep controller methods thin: parse/bind → call `services.*` → return result/error. +- DO regenerate code after changes (routes/docs/models). +- DO NOT manually edit generated files: +- `backend/app/http/**/routes.gen.go` +- `backend/app/http/**/provider.gen.go` +- `backend/docs/docs.go` +- DO keep Swagger annotations consistent with actual Fiber route paths (including `:param`). + +--- + +## 1) Add a new HTTP API endpoint + +### 1.1 Where code lives + +- Controllers: `backend/app/http//*.go` +- Example module: `backend/app/http/super/tenant.go`, `backend/app/http/super/user.go` +- DTOs: `backend/app/http//dto/*` +- Routes (generated): `backend/app/http//routes.gen.go` +- Swagger output (generated): `backend/docs/swagger.yaml`, `backend/docs/swagger.json`, `backend/docs/docs.go` + +### 1.2 Controller method signatures + +- “Return data” endpoints: return `(, error)` +- Example: `(*requests.Pager, error)` for paginated list +- “No data” endpoints: return `error` + +### 1.3 Swagger annotations (minimum set) + +Place above the handler function: + +- `@Summary` +- `@Tags` +- `@Accept json` +- `@Produce json` +- `@Param` (query/path/body as needed) +- `@Success` for 200 responses +- `@Router [get|post|patch|delete|put]` +- `@Bind` for parameters (see below) + +Common `@Success` patterns: + +- Paginated list: `requests.Pager{items=dto.Item}` +- Single object: `dto.Item` +- Array: `{array} dto.Item` + +### 1.4 Parameter binding (@Bind) + +Format: + +`@Bind [key()] [model(|[:])]` + +Positions: + +- `path`, `query`, `body`, `header`, `cookie`, `local`, `file` + +Notes: + +- `paramName` MUST match function parameter name (case-sensitive). +- Default key name is `paramName` ; override via `key(...)`. +- Scalar types: `string/int/int32/int64/float32/float64/bool`. +- Pointer types are supported (framework will handle deref for most positions). + +#### Model binding (path-only) + +Used to bind a model instance from a path value: + +- `model(id)` (recommended) +- `model(id:int)` / `model(code:string)` +- `model(pkg.Type:field)` or `model(pkg.Type)` (default field is `id`) + +Behavior: + +- Generated binder queries by field and returns first row as the parameter value. +- Auto-imports field helper for query building. + +### 1.5 Generate routes + providers + swagger docs + +Run from `backend/`: + +- Generate routes: `atomctl gen route` +- Generate providers: `atomctl gen provider` +- Generate swagger docs: `atomctl swag init` + +### 1.6 Local verify + +- Build/run: `make run` +- Use REST client examples: `backend/test/[module]/[controller].http` (extend it for new endpoints) + +### 1.7 Testing + +- Prefer existing test style under `backend/tests/e2e`. +- Run: `make test` + +--- + +## 2) Add / update a DB model + +Models live in: + +- `backend/database/models/*` (generated model code + optional manual extensions) + +### 2.1 Migration → model generation workflow + +1) Create migration: + +- `atomctl migrate create alter_table` or `atomctl migrate create create_table` + +2) Edit migration: + +- No explicit `BEGIN/COMMIT` needed (framework handles). +- Table name should be plural (e.g. `tenants`). + +3) Apply migration: + +- `atomctl migrate up` + +4) Map complex field types (JSON/ARRAY/UUID/…) via transform file: + +- `backend/database/.transform.yaml` → `field_type.` + +5) Generate models: + +- `atomctl gen model` + +### 2.2 Enum strategy + +- DO NOT use native DB ENUM. +- Define enums in Go under `backend/pkg/consts/
.go`, example: + +```go +// swagger:enum UserStatus +// ENUM(pending_verify, verified, banned, ) +type UserStatus string +``` + +- Generate enum code: `atomctl gen enum` + +### 2.3 Supported field types (`gen/types/`) + +`backend/database/.transform.yaml` typically imports `go.ipao.vip/gen` so you can use `types.*` in `field_type`. + +Common types: + +- JSON: `types.JSON`, `types.JSONMap`, `types.JSONType[T]`, `types.JSONSlice[T]` +- Array: `types.Array[T]` +- UUID: `types.UUID`, `types.BinUUID` +- Date/Time: `types.Date`, `types.Time` +- Money/XML/URL/Binary: `types.Money`, `types.XML`, `types.URL`, `types.HexBytes` +- Bit string: `types.BitString` +- Network: `types.Inet`, `types.CIDR`, `types.MACAddr` +- Ranges: `types.Int4Range`, `types.Int8Range`, `types.NumRange`, `types.TsRange`, `types.TstzRange`, `types.DateRange` +- Geometry: `types.Point`, `types.Polygon`, `types.Box`, `types.Circle`, `types.Path` +- Fulltext: `types.TSQuery`, `types.TSVector` +- Nullable: `types.Null[T]` and aliases (requires DB NULL) + +Reference: + +- Detailed examples: `gen/types/README.md` + +### 2.4 Relationships (GORM-aligned) via `.transform.yaml` + +Define in `field_relate.
.`: + +- `relation`: `belongs_to` | `has_one` | `has_many` | `many_to_many` +- `table`: target table +- `pivot`: join table (many_to_many only) +- `foreign_key`, `references` +- `join_foreign_key`, `join_references` (many_to_many only) +- `json`: JSON field name in API outputs + +Generator will convert snake_case columns to Go struct field names (e.g. `class_id` → `ClassID`). + +### 2.5 Extending generated models + +- Add manual methods/hooks by creating `backend/database/models/
.go`. +- Keep generated files untouched ; put custom logic only in your own file(s). + +--- + +## 3) Service layer injection (when adding services) + +- Services are in `backend/app/services`. +- After creating/updating a service provider, regenerate wiring: + - `atomctl gen service` + - `atomctl gen provider` +- Service call conventions: + - **Service-to-service (inside `services` package)**: call directly as `CamelCaseServiceStructName.Method()` (no `services.` prefix). + - **From outside (controllers/handlers/etc.)**: call via the package entrypoint `services.CamelCaseServiceStructName.Method()`. + +--- + +## 4) Quick command summary (run in `backend/`) + +- `make run` / `make build` / `make test` +- `atomctl gen route` / `atomctl gen provider` / `atomctl swag init` +- `atomctl migrate create ...` / `atomctl migrate up` +- `atomctl gen model` / `atomctl gen enum` / `atomctl gen service` +- `make init` (full refresh) diff --git a/backend/super.http b/backend/tests/super.http similarity index 100% rename from backend/super.http rename to backend/tests/super.http