diff --git a/.cursor/rules/module-layers.mdc b/.cursor/rules/module-layers.mdc index 4a8487d..5846f4c 100644 --- a/.cursor/rules/module-layers.mdc +++ b/.cursor/rules/module-layers.mdc @@ -11,7 +11,7 @@ Each business module under `internal/module/{biz}` is three packages: | Layer | Path | Owns | |-------|------|------| | entity | `{biz}/entity` | Domain structs, message payload types, no I/O | -| model | `{biz}/model` | One Go file per MySQL table (GORM + COMMENT tags), Redis adapters | +| model | `{biz}/model` | One Go file per MySQL table (`TableName()` → `t_*`, GORM + COMMENT tags), Redis adapters | | logic | `{biz}/logic` | One file per **function** (profile, login, send, inbox sync). Not one file per table. | `logic` must not import `model`. Wire implementations in `internal/bootstrap`. Auth has no tables (entity + logic only). diff --git a/.cursor/rules/verify-before-done.mdc b/.cursor/rules/verify-before-done.mdc index 39c7615..d319c98 100644 --- a/.cursor/rules/verify-before-done.mdc +++ b/.cursor/rules/verify-before-done.mdc @@ -7,11 +7,10 @@ alwaysApply: true After **any** change to Go code, configs, migrations, OpenAPI, or i18n catalogs in this repo: -1. Run `make lint` from the repo root (`golangci-lint`; see `.golangci.yaml`). -2. Run `make test` from the repo root (full `go test ./...` plus logic-package coverage gate). +1. Run `make test` from the repo root — it depends on `make lint` (`golangci-lint`; see `.golangci.yaml`) then `go test ./...` plus logic-package coverage (≥65%). -**Do not** tell the user the task is complete, ready to merge, or OK to deploy until **both** commands exit 0. +**Do not** tell the user the task is complete, ready to merge, or OK to deploy until `make test` exits 0. -If either fails, fix the failures and re-run both. Do not skip hooks or substitute partial checks (e.g. only `go test` on one package) unless the user explicitly asked for a scoped check. +If it fails, fix and re-run. Do not skip hooks or substitute partial checks (e.g. only `go test` on one package) unless the user explicitly asked for a scoped check. Use `make lint` alone only when debugging lint without the full test run. Docs-only or comment-only edits with zero build/test surface may skip verification; anything that can affect compile, lint, or tests must run both targets. diff --git a/.cursor/skills/spark-im-v3/SKILL.md b/.cursor/skills/spark-im-v3/SKILL.md index 201612e..963dad1 100644 --- a/.cursor/skills/spark-im-v3/SKILL.md +++ b/.cursor/skills/spark-im-v3/SKILL.md @@ -1,18 +1,20 @@ --- name: spark-im-v3 description: >- - Implements Spark IM modular monolith per architecture V3. Use when writing or - reviewing spark-server Go code, TCP/gnet, Gin HTTP, REST /v1 groups, MVC - handlers, go-playground validator, RealIP, messages, deliver queue, inbox - ZSET, Nginx 443 SNI, PROXY protocol, or SEND_ACK. Trigger terms: IM, MsgId, - TaskPool, deliver:queue, SYNC_INBOX, gnet, RealIP, validator, LoginService, - i18n, Accept-Language, zh-CN, uid, spark_id, infrastructure, entity, model, logic, - enum, MessageType, oneof. + Implements Spark IM modular monolith per architecture V3 in spark-server. Use + when writing or reviewing Go, migrations/SQL, OpenAPI, TCP/gnet, Gin HTTP, + admin `/api/admin`, REST `/v1`, MVC handlers, validator, RealIP, messages, + deliver queue, inbox ZSET, music/square modules, Makefile lint/test/deploy, + or t_* MySQL tables. Triggers: IM, MsgId, TaskPool, deliver:queue, migrations, + t_users, golangci-lint, make test, SYNC_INBOX, gnet, RealIP, i18n, entity, + model, logic, enum, oneof, SEND_ACK. --- # Spark IM V3 -Read [invariants.md](invariants.md) before changing send/sync/transport. Full design: `docs/architecture-v3.md`. Lint: `.golangci.yaml`. +Read [invariants.md](invariants.md) before changing send/sync/transport. Full design: `docs/architecture-v3.md`. Lint: `.golangci.yaml` (use `GOLANGCI_LINT := golangci-lint` in Makefile — not Make's builtin `LINT`). + +MySQL / migrations: [mysql.md](mysql.md) — **`t_` table prefix**, replay-all migrate, run migrate after SQL edits. ## Layout @@ -40,7 +42,7 @@ Each `internal/module/{biz}` is three packages. Do not put entity, GORM rows, an | Layer | Path | Owns | |-------|------|------| | entity | `{biz}/entity` | Domain types, message payloads. No I/O. | -| model | `{biz}/model` | One Go file per MySQL table (GORM + COMMENT). Redis adapters. | +| model | `{biz}/model` | One Go file per MySQL table (`TableName()` → `t_*`, GORM + COMMENT). Redis adapters. | | logic | `{biz}/logic` | One file per **function** (profile, login, send). Not per table. | `logic` must not import `model`. Wire implementations in `internal/bootstrap`. Auth has no tables (entity + logic only). HTTP/TCP both call `logic`; Gin handlers only bind and write the envelope. @@ -63,7 +65,7 @@ Not every PK is Snowflake. Do not AUTO_INCREMENT Snowflake business ids (`users. | Kind | Examples | Rule | |------|----------|------| | Snowflake | `users.id` (uid), `messages.id` (MsgId), `conversations.id` | Time-ordered business IDs. One `id.Generator` per process; `snowflake_worker_id` unique per process. Clock rewind fails issuance for the whole process. | -| AUTO_INCREMENT | `groups.id` (群号); row id on `user_devices`, `friend_relations`, `user_login_logs`, `user_emails`, `p2p_conversations`, `conversation_members`, `group_members`, `device_sync_cursors`, `message_deletes`, `moment_audience`, `moment_likes`, `moment_settings`, `moment_hides` | Table-local row id. Business uniqueness stays UNIQUE (email, P2P pair, members, cursors). Do not AUTO_INCREMENT Snowflake columns. | +| AUTO_INCREMENT | `t_groups.id` (群号); row id on `t_user_devices`, `t_friend_relations`, … (see entity docs) | Table-local row id. Business uniqueness stays UNIQUE (email, P2P pair, members, cursors). Do not AUTO_INCREMENT Snowflake columns. | `device_id` is **client-generated**, stored on the device, written to JWT `did`. The server does not allocate it. `uid` in JSON **is** `users.id`; do not add a second uid column. @@ -105,23 +107,16 @@ AUTH/login rate-limit uses `reqctx.RealIP`. TaskPool workers recover panics. Del - HTTP: thin handler + module `logic`; REST group by business; validate with i18n `label`/`errmsg` keys; put RealIP and language on context. - Closed type/status: entity typed enum + `Valid()` + OpenAPI named `$ref` + validator `oneof` on the same values ([enums.md](enums.md)). - Login is email OTP (`POST /v1/auth/login`). `uid` is Snowflake `users.id`; public `spark_id` is unique and mutable. Email↔user lives in `user_emails`. Login upserts `user_devices` (client `device_id`). Logout is `POST /v1/auth/logout` (current device). Account cancellation is `DELETE /v1/users/me` (archive to `user_delete`, purge friends/moments/related). -- SQL tables and GORM models carry COMMENT on the table and every column. -- After adding or changing `migrations/*.sql`, run `go run ./cmd/migrate -config configs/config.yaml` in this session. Do not leave it for deploy. +- SQL/GORM: [mysql.md](mysql.md) (COMMENT, `t_*`, migrate). -## Before declaring done (required) +## Verify before done -After modifying this repo, run from the project root: - -```bash -make lint -make test -``` - -Both must exit 0. `make test` runs all packages and enforces logic-layer coverage (see root `Makefile`). Do not mark work complete or hand off to the user until lint and test pass. Fix failures and re-run. +Follow `.cursor/rules/verify-before-done.mdc`: from repo root, `make test` (depends on `lint`) must exit 0; logic packages need ≥65% coverage (`LOGIC_COVER_MIN`). `make deploy` depends on `make test`. Docs-only edits with no compile surface may skip. ## Additional resources - [invariants.md](invariants.md) +- [mysql.md](mysql.md) - [send-path.md](send-path.md) - [http.md](http.md) - [i18n.md](i18n.md) diff --git a/.cursor/skills/spark-im-v3/enums.md b/.cursor/skills/spark-im-v3/enums.md index 45d9c17..689ca0d 100644 --- a/.cursor/skills/spark-im-v3/enums.md +++ b/.cursor/skills/spark-im-v3/enums.md @@ -6,6 +6,8 @@ Closed type/status values live in `internal/module/{biz}/entity` as named types. Wire JSON and TCP stay **numbers** (or the existing strings below). Do not turn message types into strings. Do not change persisted numeric values. +Physical MySQL tables are **`t_*`**; catalog column names below use logical names (`users.gender` → table `t_users`). + ## Template (`message/entity.Type`) ```go diff --git a/.cursor/skills/spark-im-v3/http.md b/.cursor/skills/spark-im-v3/http.md index 6879ea9..c52ef53 100644 --- a/.cursor/skills/spark-im-v3/http.md +++ b/.cursor/skills/spark-im-v3/http.md @@ -89,6 +89,8 @@ Public HTTP is `/v1/{resource}/...`. Register with Gin groups in `internal/trans | files | `/v1/files` | upload credential | | favorites | `/v1/favorites` | 收藏聊天消息 | | moments | `/v1/moments` | 朋友圈 Feed、相册、赞评、设置 | +| square | `/v1/square` | 广场帖子、赞评、通知 | +| music | `/v1/music` | 公开 playlist / categories / settings(无需登录) | | meta | `/v1/meta` | `GET /v1/meta/enums`(无需登录) | Rules: @@ -107,6 +109,19 @@ Documented in **`docs/sms.md`**. Implementation: Both return **`errs.ErrRateLimited` (`42902`)**. UI countdown should follow **60s** target cooldown, not `expires_in`. - OpenAPI `tags` must match these groups. Docs jump tags `enums` / `errors` sit above `system` and open `/docs/enums`、`/docs/errors`. +## Admin HTTP (separate from `/v1`) + +Registered in `router_admin.go`. Bearer admin token (`AdminAuth`). Prefixes: + +| Prefix | Use | +|--------|-----| +| `/api/admin` | Shared user lookup / ban ops (popadmin host routing) | +| `/api/admin/spark`, `/api/admin/pop` | IM stats, groups, complaints, rankings, **music CRUD** + uploads | + +Music admin: `GET/POST/PATCH/DELETE …/music`, categories tree, `PUT …/music/:id/categories`, cover/background/audio upload. Client-facing music stays on `/v1/music`. + +When adding admin routes: handler under `handler/admin`, register in `router_admin.go`, document in OpenAPI admin section if applicable. + ## Checklist when adding an HTTP API 1. Input struct in `internal/module/{biz}` with `validate` / `label` / `errmsg` **keys**. diff --git a/.cursor/skills/spark-im-v3/mysql.md b/.cursor/skills/spark-im-v3/mysql.md new file mode 100644 index 0000000..59ba80f --- /dev/null +++ b/.cursor/skills/spark-im-v3/mysql.md @@ -0,0 +1,34 @@ +# MySQL: `t_` tables, migrations, GORM + +## Table names + +All application tables use the **`t_` prefix** (`t_users`, `t_messages`, `t_music_tracks`, …). + +| Layer | Rule | +|-------|------| +| SQL migrations | `CREATE TABLE t_*`, `RENAME TABLE … TO t_*` | +| GORM | `func (Row) TableName() string { return "t_…" }` on every row struct | +| Raw SQL in logic/model | Use `t_*` names; do not reference unprefixed legacy names | + +Legacy DBs: `migrations/0000_table_prefix.sql` renames old tables before `0001+` (no-op if already prefixed). New environments rely on `CREATE t_*` in later files. + +## Migrations workflow + +- Files live in `migrations/*.sql`; **`cmd/migrate` replays every file** on each run (no version table). Order matters: `0000` before creates. +- Every new/changed migration: run in the agent session: + +```bash +go run ./cmd/migrate -config configs/config.yaml +``` + +Do not tell the user to apply SQL manually on deploy unless they asked for ops-only steps. + +## Schema documentation + +- Table-level and column-level **`COMMENT`** in SQL; mirror intent in GORM struct tags where the team already uses comments. +- Closed numeric/string sets: entity enum + OpenAPI `$ref` ([enums.md](enums.md)), not ad hoc magic numbers in SQL. + +## Logic tests vs DB + +- `internal/module/*/logic` tests use stubs/fakes; they must **not** require MySQL. +- Model/repo integration belongs in transport tests or dedicated DB tests if added later. diff --git a/.cursor/skills/spark-im-v3/send-path.md b/.cursor/skills/spark-im-v3/send-path.md index 3e360cb..2126b71 100644 --- a/.cursor/skills/spark-im-v3/send-path.md +++ b/.cursor/skills/spark-im-v3/send-path.md @@ -25,7 +25,7 @@ _ = pool.Submit(ctx, func() { router.Dispatch(ctx, frame) }) ## Sync ```sql -SELECT * FROM messages +SELECT * FROM t_messages WHERE conversation_id = ? AND id > ? ORDER BY id ASC LIMIT 200; ``` diff --git a/AGENTS.md b/AGENTS.md index 2f0ef26..9065481 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,8 +7,8 @@ Before writing code: 1. Load `.cursor/skills/spark-im-v3/SKILL.md` 2. Follow `.cursor/rules/` (always-on V3 invariants + file-scoped rules) 3. Keep functions within `.golangci.yaml` (`funlen` 100, `lll` 240) -4. HTTP and TCP share Application services under `internal/module/{biz}/logic`. Each module is `entity` / `model` (one file per table) / `logic` (one file per function, not per table). Gin handlers only bind and call those services (MVC). REST paths are `/v1/{resource}` grouped by business. Request DTOs use go-playground/validator (`label` / `errmsg` i18n keys). Context must carry `reqctx.RealIP` and the user language (`zh-CN` / `en` / `zh-TW`). Error `msg` and notification copy come from `internal/pkg/i18n`. Table and column COMMENT is required. Message `content` is serialized bytes, not a string. -5. After adding or changing `migrations/*.sql`, run `go run ./cmd/migrate -config configs/config.yaml` yourself. Do not ask the user to apply it later. -6. Before finishing any code change, run `make lint` and `make test` at the repo root; both must pass. Do not report the task as OK until they do. +4. HTTP and TCP share Application services under `internal/module/{biz}/logic`. Each module is `entity` / `model` (one file per table, GORM `TableName()` → `t_*`) / `logic` (one file per function, not per table). Gin handlers only bind and call those services (MVC). REST paths are `/v1/{resource}` grouped by business; admin ops use `/api/admin/…` (see skill `http.md`). Request DTOs use go-playground/validator (`label` / `errmsg` i18n keys). Context must carry `reqctx.RealIP` and the user language (`zh-CN` / `en` / `zh-TW`). Error `msg` and notification copy come from `internal/pkg/i18n`. Table and column COMMENT is required. Message `content` is serialized bytes, not a string. +5. MySQL: all tables `t_*`; migrations replay-all — details in `.cursor/skills/spark-im-v3/mysql.md`. After changing `migrations/*.sql`, run `go run ./cmd/migrate -config configs/config.yaml` yourself. +6. Before finishing any code change, run `make test` at the repo root (runs `lint` first, logic coverage ≥65%). Do not report the task as OK until it passes. `make deploy` chains through `test`. Do not introduce Kafka, K8s, microservices, ConversationSeq, application-layer handshake crypto, or public gnet ports.