Compare commits
2 Commits
63107ca23f
...
40776b78e2
| Author | SHA1 | Date | |
|---|---|---|---|
| 40776b78e2 | |||
| 469d691a75 |
35
AGENTS.md
Normal file
35
AGENTS.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
|
||||
- `backend/`: Go API server (Fiber) + services + DB models/migrations.
|
||||
- `backend/app/http/`: HTTP modules (e.g. `super/`, `tenant/`, `tenant_public/`).
|
||||
- `backend/app/services/`: business logic; prefer GORM-Gen queries (`backend/database/models/*`).
|
||||
- `backend/database/migrations/`: SQL migrations.
|
||||
- `backend/docs/`: generated Swagger (`swagger.json/yaml`, `docs.go`).
|
||||
- `frontend/`:
|
||||
- `frontend/superadmin/`: platform super admin UI (Vite + Vue 3 + PrimeVue).
|
||||
- `frontend/admin/`, `frontend/user/`: tenant admin/user apps.
|
||||
- `specs/`: source-of-truth specs (e.g. DB schema).
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
|
||||
- **Backend rule of law**: all backend development MUST follow `backend/llm.txt` (HTTP/module layout, generated-file rules, GORM-Gen usage, transactions, comments, and route conventions).
|
||||
- Go: run `gofmt` on changed files; keep HTTP handlers thin (bind → `services.*` → return).
|
||||
- HTTP module directories are `snake_case`; path params are `camelCase` and prefer typed IDs like `:orderID<int>` to avoid route conflicts.
|
||||
- Avoid editing generated files (e.g. `backend/app/http/**/routes.gen.go`, `backend/docs/docs.go`); regenerate via `atomctl` instead.
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
- Go tests: `go test ./...` (some service tests exist under `backend/app/services/*_test.go`).
|
||||
- Frontend: build + lint are the main checks (`npm -C frontend/superadmin run build && npm -C frontend/superadmin run lint`).
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
- Commits generally follow a simple convention like `feat: ...` / `fix: ...` / `chore: ...` (keep subject short and imperative).
|
||||
- PRs: describe behavior change, list impacted APIs/pages, and include screenshots for UI changes (especially `frontend/superadmin/`).
|
||||
|
||||
## Security & Configuration Tips
|
||||
|
||||
- Configure API base via `VITE_API_BASE_URL` (superadmin).
|
||||
- Keep secrets out of git; prefer local `backend/config.toml` overrides for DB/JWT settings.
|
||||
@@ -41,7 +41,7 @@ func (*order) list(ctx fiber.Ctx, filter *dto.OrderPageFilter) (*requests.Pager,
|
||||
// @Param orderID path int64 true "OrderID"
|
||||
// @Success 200 {object} dto.SuperOrderDetail
|
||||
//
|
||||
// @Router /super/v1/orders/:orderID [get]
|
||||
// @Router /super/v1/orders/:orderID<int> [get]
|
||||
// @Bind orderID path
|
||||
func (*order) detail(ctx fiber.Ctx, orderID int64) (*dto.SuperOrderDetail, error) {
|
||||
return services.Order.SuperOrderDetail(ctx, orderID)
|
||||
@@ -58,7 +58,7 @@ func (*order) detail(ctx fiber.Ctx, orderID int64) (*dto.SuperOrderDetail, error
|
||||
// @Param form body dto.SuperOrderRefundForm true "Form"
|
||||
// @Success 200 {object} models.Order
|
||||
//
|
||||
// @Router /super/v1/orders/:orderID/refund [post]
|
||||
// @Router /super/v1/orders/:orderID<int>/refund [post]
|
||||
// @Bind orderID path
|
||||
// @Bind form body
|
||||
func (*order) refund(ctx fiber.Ctx, orderID int64, form *dto.SuperOrderRefundForm) (*models.Order, error) {
|
||||
|
||||
@@ -61,8 +61,8 @@ func (r *Routes) Register(router fiber.Router) {
|
||||
r.order.list,
|
||||
Query[dto.OrderPageFilter]("filter"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /super/v1/orders/:orderID -> order.detail")
|
||||
router.Get("/super/v1/orders/:orderID"[len(r.Path()):], DataFunc1(
|
||||
r.log.Debugf("Registering route: Get /super/v1/orders/:orderID<int> -> order.detail")
|
||||
router.Get("/super/v1/orders/:orderID<int>"[len(r.Path()):], DataFunc1(
|
||||
r.order.detail,
|
||||
PathParam[int64]("orderID"),
|
||||
))
|
||||
@@ -70,8 +70,8 @@ func (r *Routes) Register(router fiber.Router) {
|
||||
router.Get("/super/v1/orders/statistics"[len(r.Path()):], DataFunc0(
|
||||
r.order.statistics,
|
||||
))
|
||||
r.log.Debugf("Registering route: Post /super/v1/orders/:orderID/refund -> order.refund")
|
||||
router.Post("/super/v1/orders/:orderID/refund"[len(r.Path()):], DataFunc2(
|
||||
r.log.Debugf("Registering route: Post /super/v1/orders/:orderID<int>/refund -> order.refund")
|
||||
router.Post("/super/v1/orders/:orderID<int>/refund"[len(r.Path()):], DataFunc2(
|
||||
r.order.refund,
|
||||
PathParam[int64]("orderID"),
|
||||
Body[dto.SuperOrderRefundForm]("form"),
|
||||
@@ -82,8 +82,8 @@ func (r *Routes) Register(router fiber.Router) {
|
||||
r.tenant.list,
|
||||
Query[dto.TenantFilter]("filter"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /super/v1/tenants/:tenantID/users -> tenant.users")
|
||||
router.Get("/super/v1/tenants/:tenantID/users"[len(r.Path()):], DataFunc2(
|
||||
r.log.Debugf("Registering route: Get /super/v1/tenants/:tenantID<int>/users -> tenant.users")
|
||||
router.Get("/super/v1/tenants/:tenantID<int>/users"[len(r.Path()):], DataFunc2(
|
||||
r.tenant.users,
|
||||
PathParam[int64]("tenantID"),
|
||||
Query[tenantdto.AdminTenantUserListFilter]("filter"),
|
||||
@@ -92,14 +92,14 @@ func (r *Routes) Register(router fiber.Router) {
|
||||
router.Get("/super/v1/tenants/statuses"[len(r.Path()):], DataFunc0(
|
||||
r.tenant.statusList,
|
||||
))
|
||||
r.log.Debugf("Registering route: Patch /super/v1/tenants/:tenantID -> tenant.updateExpire")
|
||||
router.Patch("/super/v1/tenants/:tenantID"[len(r.Path()):], Func2(
|
||||
r.log.Debugf("Registering route: Patch /super/v1/tenants/:tenantID<int> -> tenant.updateExpire")
|
||||
router.Patch("/super/v1/tenants/:tenantID<int>"[len(r.Path()):], Func2(
|
||||
r.tenant.updateExpire,
|
||||
PathParam[int64]("tenantID"),
|
||||
Body[dto.TenantExpireUpdateForm]("form"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Patch /super/v1/tenants/:tenantID/status -> tenant.updateStatus")
|
||||
router.Patch("/super/v1/tenants/:tenantID/status"[len(r.Path()):], Func2(
|
||||
r.log.Debugf("Registering route: Patch /super/v1/tenants/:tenantID<int>/status -> tenant.updateStatus")
|
||||
router.Patch("/super/v1/tenants/:tenantID<int>/status"[len(r.Path()):], Func2(
|
||||
r.tenant.updateStatus,
|
||||
PathParam[int64]("tenantID"),
|
||||
Body[dto.TenantStatusUpdateForm]("form"),
|
||||
@@ -115,8 +115,8 @@ func (r *Routes) Register(router fiber.Router) {
|
||||
r.user.list,
|
||||
Query[dto.UserPageFilter]("filter"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /super/v1/users/:userID/tenants -> user.tenants")
|
||||
router.Get("/super/v1/users/:userID/tenants"[len(r.Path()):], DataFunc2(
|
||||
r.log.Debugf("Registering route: Get /super/v1/users/:userID<int>/tenants -> user.tenants")
|
||||
router.Get("/super/v1/users/:userID<int>/tenants"[len(r.Path()):], DataFunc2(
|
||||
r.user.tenants,
|
||||
PathParam[int64]("userID"),
|
||||
Query[dto.UserTenantPageFilter]("filter"),
|
||||
@@ -129,14 +129,14 @@ func (r *Routes) Register(router fiber.Router) {
|
||||
router.Get("/super/v1/users/statuses"[len(r.Path()):], DataFunc0(
|
||||
r.user.statusList,
|
||||
))
|
||||
r.log.Debugf("Registering route: Patch /super/v1/users/:userID/roles -> user.updateRoles")
|
||||
router.Patch("/super/v1/users/:userID/roles"[len(r.Path()):], Func2(
|
||||
r.log.Debugf("Registering route: Patch /super/v1/users/:userID<int>/roles -> user.updateRoles")
|
||||
router.Patch("/super/v1/users/:userID<int>/roles"[len(r.Path()):], Func2(
|
||||
r.user.updateRoles,
|
||||
PathParam[int64]("userID"),
|
||||
Body[dto.UserRolesUpdateForm]("form"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Patch /super/v1/users/:userID/status -> user.updateStatus")
|
||||
router.Patch("/super/v1/users/:userID/status"[len(r.Path()):], Func2(
|
||||
r.log.Debugf("Registering route: Patch /super/v1/users/:userID<int>/status -> user.updateStatus")
|
||||
router.Patch("/super/v1/users/:userID<int>/status"[len(r.Path()):], Func2(
|
||||
r.user.updateStatus,
|
||||
PathParam[int64]("userID"),
|
||||
Body[dto.UserStatusUpdateForm]("form"),
|
||||
|
||||
@@ -55,7 +55,7 @@ func (*tenant) create(ctx fiber.Ctx, form *dto.TenantCreateForm) (*models.Tenant
|
||||
// @Param filter query tenantdto.AdminTenantUserListFilter true "Filter"
|
||||
// @Success 200 {object} requests.Pager{items=dto.SuperTenantUserItem}
|
||||
//
|
||||
// @Router /super/v1/tenants/:tenantID/users [get]
|
||||
// @Router /super/v1/tenants/:tenantID<int>/users [get]
|
||||
// @Bind tenantID path
|
||||
// @Bind filter query
|
||||
func (*tenant) users(ctx fiber.Ctx, tenantID int64, filter *tenantdto.AdminTenantUserListFilter) (*requests.Pager, error) {
|
||||
@@ -71,7 +71,7 @@ func (*tenant) users(ctx fiber.Ctx, tenantID int64, filter *tenantdto.AdminTenan
|
||||
// @Param tenantID path int64 true "TenantID"
|
||||
// @Param form body dto.TenantExpireUpdateForm true "Form"
|
||||
//
|
||||
// @Router /super/v1/tenants/:tenantID [patch]
|
||||
// @Router /super/v1/tenants/:tenantID<int> [patch]
|
||||
// @Bind tenantID path
|
||||
// @Bind form body
|
||||
func (*tenant) updateExpire(ctx fiber.Ctx, tenantID int64, form *dto.TenantExpireUpdateForm) error {
|
||||
@@ -92,7 +92,7 @@ func (*tenant) updateExpire(ctx fiber.Ctx, tenantID int64, form *dto.TenantExpir
|
||||
// @Param tenantID path int64 true "TenantID"
|
||||
// @Param form body dto.TenantStatusUpdateForm true "Form"
|
||||
//
|
||||
// @Router /super/v1/tenants/:tenantID/status [patch]
|
||||
// @Router /super/v1/tenants/:tenantID<int>/status [patch]
|
||||
// @Bind tenantID path
|
||||
// @Bind form body
|
||||
func (*tenant) updateStatus(ctx fiber.Ctx, tenantID int64, form *dto.TenantStatusUpdateForm) error {
|
||||
|
||||
@@ -38,7 +38,7 @@ func (*user) list(ctx fiber.Ctx, filter *dto.UserPageFilter) (*requests.Pager, e
|
||||
// @Param filter query dto.UserTenantPageFilter true "Filter"
|
||||
// @Success 200 {object} requests.Pager{items=dto.UserTenantItem}
|
||||
//
|
||||
// @Router /super/v1/users/:userID/tenants [get]
|
||||
// @Router /super/v1/users/:userID<int>/tenants [get]
|
||||
// @Bind userID path
|
||||
// @Bind filter query
|
||||
func (*user) tenants(ctx fiber.Ctx, userID int64, filter *dto.UserTenantPageFilter) (*requests.Pager, error) {
|
||||
@@ -54,7 +54,7 @@ func (*user) tenants(ctx fiber.Ctx, userID int64, filter *dto.UserTenantPageFilt
|
||||
// @Param userID path int64 true "UserID"
|
||||
// @Param form body dto.UserStatusUpdateForm true "Form"
|
||||
//
|
||||
// @Router /super/v1/users/:userID/status [patch]
|
||||
// @Router /super/v1/users/:userID<int>/status [patch]
|
||||
// @Bind userID path
|
||||
// @Bind form body
|
||||
func (*user) updateStatus(ctx fiber.Ctx, userID int64, form *dto.UserStatusUpdateForm) error {
|
||||
@@ -70,7 +70,7 @@ func (*user) updateStatus(ctx fiber.Ctx, userID int64, form *dto.UserStatusUpdat
|
||||
// @Param userID path int64 true "UserID"
|
||||
// @Param form body dto.UserRolesUpdateForm true "Form"
|
||||
//
|
||||
// @Router /super/v1/users/:userID/roles [patch]
|
||||
// @Router /super/v1/users/:userID<int>/roles [patch]
|
||||
// @Bind userID path
|
||||
// @Bind form body
|
||||
func (*user) updateRoles(ctx fiber.Ctx, userID int64, form *dto.UserRolesUpdateForm) error {
|
||||
|
||||
@@ -23,6 +23,7 @@ This file condenses `backend/docs/dev/http_api.md` + `backend/docs/dev/model.md`
|
||||
- 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: for numeric ID path params (`int/int64` like `tenantID/userID/orderID`), prefer Fiber typed params `:tenantID<int>` to avoid conflicts with static subpaths (e.g. `/orders/statistics`) and reduce ambiguous routing.
|
||||
- MUST: when importing another HTTP module's `dto` package, the import alias MUST be `<module>_dto` (e.g. `tenant_dto`), not `<module>dto` (e.g. `tenantdto`).
|
||||
- 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).
|
||||
- MUST: business code comments MUST be written in Chinese (中文注释), to keep review/maintenance consistent across the team.
|
||||
|
||||
Reference in New Issue
Block a user