233 lines
8.7 KiB
Plaintext
233 lines
8.7 KiB
Plaintext
# 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/<module>/`.
|
||
- DO keep controller methods thin: parse/bind → call `services.*` → return result/error.
|
||
- DO regenerate code after changes (routes/docs/models).
|
||
- DO add `// @provider` above every controller/service `struct` declaration.
|
||
- DO keep HTTP middlewares in `backend/app/middlewares/` only.
|
||
- DO keep all `const` declarations in `backend/pkg/consts/` only (do not declare constants elsewhere).
|
||
- DO NOT manually edit generated files:
|
||
- `backend/app/http/**/routes.gen.go`
|
||
- `backend/app/http/**/provider.gen.go`
|
||
- `backend/docs/docs.go`
|
||
- DO NOT manually write provider declarations (only `atomctl gen provider`).
|
||
- DO NOT manually write route declarations (only `atomctl gen route`).
|
||
- DO keep Swagger annotations consistent with actual Fiber route paths (including `:param`).
|
||
- MUST: route path parameter placeholders MUST be `camelCase` (e.g. `:tenantCode`), never `snake_case` (e.g. `:tenant_code`).
|
||
- MUST: when creating/generating Go `struct` definitions (DTOs/requests/responses/etc.), add detailed per-field comments describing meaning, usage scenario, and validation/usage rules (do not rely on “self-explanatory” names).
|
||
|
||
---
|
||
|
||
## 1) Add a new HTTP API endpoint
|
||
|
||
### 1.1 Where code lives
|
||
|
||
- Controllers: `backend/app/http/<module>/*.go`
|
||
- Example module: `backend/app/http/super/tenant.go`, `backend/app/http/super/user.go`
|
||
- DTOs: `backend/app/http/<module>/dto/*`
|
||
- HTTP middlewares: `backend/app/middlewares/*`
|
||
- Routes (generated): `backend/app/http/<module>/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 `(<T>, 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 <path> [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 <paramName> <position> [key(<key>)] [model(<field>|<type>[:<field>])]`
|
||
|
||
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: `tests/[module]/[controller].http` (extend it for new endpoints)
|
||
|
||
### 1.7 Testing
|
||
|
||
- Prefer existing test style under `backend/tests/e2e`.
|
||
- Run: `make test`
|
||
|
||
### 1.8 Module-level route group (Path + Middlewares)
|
||
|
||
If you need to define a module HTTP middleware (applies to the module route group):
|
||
|
||
1) Run `atomctl gen route` first.
|
||
2) Edit `backend/app/http/<module>/routes.manual.go`:
|
||
- Update `Path()` to return the current module route group prefix (must match the prefix used in `routes.gen.go`, e.g. `/super/v1`, `/t/:tenantCode/v1`).
|
||
- Update `Middlewares()` return value: return a list like `[]any{r.middlewares.MiddlewareFunc1, r.middlewares.MiddlewareFunc2, ...}` (no `(...)`), where each item is `r.middlewares.<MiddlewareFunc>` referencing middleware definitions in `backend/app/middlewares`.
|
||
|
||
---
|
||
|
||
## 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`).
|
||
- MUST: when writing migration content, every field/column MUST include a brief Chinese remark, and also include commented details for that field’s usage scenario and rules/constraints (e.g., valid range/format, default behavior, special cases).
|
||
|
||
3) Apply migration:
|
||
|
||
- `atomctl migrate up`
|
||
|
||
4) Map complex field types (JSON/ARRAY/UUID/…) via transform file:
|
||
|
||
- `backend/database/.transform.yaml` → `field_type.<table>`
|
||
|
||
5) Generate models:
|
||
|
||
- `atomctl gen model`
|
||
|
||
### 2.2 Enum strategy
|
||
|
||
- DO NOT use native DB ENUM.
|
||
- Define enums in Go under `backend/pkg/consts/<table>.go`, example:
|
||
|
||
```go
|
||
// swagger:enum UserStatus
|
||
// ENUM(pending_verify, verified, banned, )
|
||
type UserStatus string
|
||
```
|
||
|
||
- For every enum `type` defined under `backend/pkg/consts/`, you MUST also define:
|
||
- `Description() string`: return the Chinese label for the specific enum value (used by API/FE display).
|
||
- `Items() []requests.KV`: return the KV list for FE dropdowns (typically `Key=enum string`, `Value=Description()`). Prefer defining it as a type method like `func (UserStatus) Items() []requests.KV`.
|
||
- Prefer `string(t)` as `Key`, and use a stable default label for unknown values (e.g. `未知` / `未知状态`).
|
||
- MUST: these two methods MUST be placed immediately below the enum `type` definition (same file, directly under `type Xxx string`), to keep the enum self-contained and easy to review.
|
||
|
||
- 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.<table>.<FieldName>`:
|
||
|
||
- `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/<table>.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`
|
||
- Injection rule: provider injected dependencies MUST be `success`. do not add business-level fallbacks for injection objects nil check.
|
||
- 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)
|