mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-11 01:36:37 +08:00
fix(persistence): migrate all pkg/persistence imports to plugins/infra/database and plugins/infra/cache
- Replace db.DB(ctx) with database.DB(ctx) from plugins/infra/database
- Replace db.Redis/db.PrefixedKey/db.GetJSON/db.SetJSON with cachepkg.* from plugins/infra/cache
- Replace pkg/persistence/idgen with pkg/idgen (already exists)
- Replace pkg/persistence/batchwriter with pkg/batchwriter (already exists)
- Replace pkg/persistence/migrator with pkg/migrator (already exists)
- Replace pkg/persistence/logstore with plugins/domain/risk_control/logstore
- Delete defunct pkg/{persistence,cap,message_gateway,push,shared,task}
- Fix vet issues: db alias in domain_test.go, driver_asynq_worker.TaskHandler reference
- Update Makefile architecture guard
- Update docs and skill references
- Update go.mod: gorilla/sessions promotion to direct dependency
This commit is contained in:
@@ -1,253 +0,0 @@
|
||||
# Frontend i18n Design
|
||||
|
||||
Date: 2026-07-24
|
||||
Status: Approved for implementation planning
|
||||
Scope: Frontend UI only
|
||||
|
||||
## 1. Goals
|
||||
|
||||
Add bilingual UI support for Wavelet frontend:
|
||||
|
||||
- Languages: `zh-CN` and `en`
|
||||
- Default locale: `zh-CN`
|
||||
- Locale resolution: explicit user choice → browser language → default
|
||||
- Phase 1: infrastructure + core paths only (layout / auth / settings)
|
||||
- Must remain compatible with `NEXT_STANDALONE_EXPORT` static export
|
||||
|
||||
### Non-goals (Phase 1)
|
||||
|
||||
- Backend API error / message localization
|
||||
- Email / push notification localization
|
||||
- URL locale prefixes (`/en/...`, `/zh-CN/...`) and SEO hreflang
|
||||
- Full translation of all admin business pages
|
||||
|
||||
## 2. Context
|
||||
|
||||
Current state:
|
||||
|
||||
- Root layout hardcodes `lang='zh-CN'`
|
||||
- UI copy is mostly Chinese string literals across many TSX files
|
||||
- Date formatting often hardcodes `zh-CN` / `date-fns/locale` `zhCN`
|
||||
- No i18n library is installed
|
||||
- Frontend supports both normal Next rewrites mode and static export embed mode
|
||||
|
||||
## 3. Approach
|
||||
|
||||
Use **next-intl in non-routing / provider mode**.
|
||||
|
||||
Why this approach:
|
||||
|
||||
- Mature App Router integration and clear `useTranslations` API
|
||||
- ICU message format ready when needed
|
||||
- Avoids locale-prefixed routing, which conflicts with static-export simplicity and current route structure
|
||||
- Cookie + browser detection matches product preference without SEO path requirements
|
||||
|
||||
Rejected alternatives:
|
||||
|
||||
- Fully custom Context + JSON: lower dependency cost, but reimplements interpolation/plurals/type safety poorly
|
||||
- `i18next` + `react-i18next`: powerful, but heavier and less natural for this Next App Router setup
|
||||
|
||||
## 4. Architecture
|
||||
|
||||
```
|
||||
RootLayout
|
||||
html lang={locale}
|
||||
ThemeProvider
|
||||
CustomThemeProvider
|
||||
AppQueryProvider
|
||||
NextIntlClientProvider(locale, messages)
|
||||
existing User / Notification / Bell providers
|
||||
pages + components
|
||||
```
|
||||
|
||||
### Key files
|
||||
|
||||
| Path | Responsibility |
|
||||
| --- | --- |
|
||||
| `frontend/i18n/config.ts` | Supported locales, default locale, cookie name, normalize helpers |
|
||||
| `frontend/i18n/request.ts` | `getRequestConfig` for server-side locale/messages resolution when not in export mode |
|
||||
| `frontend/i18n/client.ts` | Client helpers to read/write locale preference |
|
||||
| `frontend/messages/zh-CN.json` | Chinese messages |
|
||||
| `frontend/messages/en.json` | English messages |
|
||||
| `frontend/components/common/language-switcher.tsx` (or under `layout/`) | Language switch UI |
|
||||
| `frontend/lib/i18n-format.ts` (optional location under `i18n/`) | Locale-aware date/number formatting helpers |
|
||||
|
||||
### Runtime flow
|
||||
|
||||
1. Resolve locale: cookie `NEXT_LOCALE` → browser languages → `zh-CN`
|
||||
2. Load `messages/{locale}.json`
|
||||
3. Provide locale + messages through `NextIntlClientProvider`
|
||||
4. Components call `useTranslations('<namespace>')`
|
||||
5. Language switcher writes cookie and refreshes locale/messages
|
||||
6. Update `document.documentElement.lang`
|
||||
|
||||
## 5. Locale Resolution
|
||||
|
||||
Supported locales: `zh-CN`, `en`
|
||||
|
||||
Normalization:
|
||||
|
||||
- `zh`, `zh-CN`, `zh-Hans*` → `zh-CN`
|
||||
- `en`, `en-US`, `en-GB`, other `en-*` → `en`
|
||||
- anything else → `zh-CN`
|
||||
|
||||
Priority:
|
||||
|
||||
1. User explicit choice stored in cookie `NEXT_LOCALE`
|
||||
2. Browser language (`Accept-Language` on server, `navigator.languages` on client)
|
||||
3. Default `zh-CN`
|
||||
|
||||
Invalid cookie values are normalized to a supported locale and may be rewritten to a valid value.
|
||||
|
||||
## 6. Static Export Compatibility
|
||||
|
||||
Constraints:
|
||||
|
||||
- No locale-segment routes
|
||||
- No middleware-based locale rewriting required for correctness
|
||||
- `build:embed` (`NEXT_STANDALONE_EXPORT=true`) must continue to work
|
||||
|
||||
Behavior:
|
||||
|
||||
- **Normal SSR/dev**: resolve locale on server when possible to reduce first-paint language flash
|
||||
- **Static export**: ship both message catalogs; resolve on client from cookie/browser; accept a brief default-language flash similar to theme hydration, using existing `suppressHydrationWarning` patterns where needed
|
||||
|
||||
## 7. Message Organization
|
||||
|
||||
Single catalog files with nested namespaces:
|
||||
|
||||
```json
|
||||
{
|
||||
"common": {
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
"loading": "加载中..."
|
||||
},
|
||||
"layout": {
|
||||
"nav": {
|
||||
"home": "首页",
|
||||
"myFiles": "我的文件"
|
||||
},
|
||||
"userMenu": {
|
||||
"settings": "设置",
|
||||
"logout": "退出登录"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"login": {
|
||||
"title": "登录",
|
||||
"submit": "登录"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"appearance": {
|
||||
"language": "语言",
|
||||
"languageDesc": "选择界面显示语言"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Conventions:
|
||||
|
||||
- Keys use camelCase and hierarchical grouping
|
||||
- Prefer complete phrases as values; avoid assembling sentences in components
|
||||
- Use ICU only when needed (`{name}`, plural forms)
|
||||
- Backend `error_msg` values are shown as-is in Phase 1
|
||||
- Frontend-owned toast / validation copy is translated
|
||||
|
||||
Both locale files must keep the same key tree. A key-alignment check script is recommended.
|
||||
|
||||
## 8. Language Switcher UX
|
||||
|
||||
Placement:
|
||||
|
||||
- Header toolbar near theme controls
|
||||
- Appearance settings page as an explicit preference row
|
||||
|
||||
UI labels for language options use native names and do not themselves translate:
|
||||
|
||||
- `中文`
|
||||
- `English`
|
||||
|
||||
On change:
|
||||
|
||||
1. Persist `NEXT_LOCALE`
|
||||
2. Apply new locale/messages (via refresh or controlled provider update)
|
||||
3. Sync `document.documentElement.lang`
|
||||
4. Preserve unrelated UI state where practical (theme, auth session, sidebar collapse)
|
||||
|
||||
## 9. Phase 1 Migration Scope
|
||||
|
||||
### In scope
|
||||
|
||||
- Install and wire `next-intl`
|
||||
- Message catalogs for core namespaces
|
||||
- Locale resolution + persistence
|
||||
- `LanguageSwitcher`
|
||||
- Translate:
|
||||
- layout shell: sidebar nav/user menu, header accessible labels / titles
|
||||
- auth: login / register / OTP labels, buttons, validation messages
|
||||
- settings: appearance (including language preference), profile, security, notifications, access-token visible copy
|
||||
- Replace date/number hardcoding only where touched by the above paths
|
||||
- Ensure `html lang` reflects active locale
|
||||
|
||||
### Out of scope
|
||||
|
||||
- Remaining admin pages and deep business modules
|
||||
- Backend localization
|
||||
- Route prefixing / SEO alternate links
|
||||
|
||||
Unmigrated pages may remain Chinese hard-coded; mixed-language UI is acceptable during incremental rollout.
|
||||
|
||||
## 10. Formatting Helpers
|
||||
|
||||
Introduce locale-aware helpers for dates/numbers used by migrated surfaces, e.g.:
|
||||
|
||||
- `formatDateTime(value, locale)`
|
||||
- `formatNumber(value, locale)`
|
||||
|
||||
`date-fns` locale objects should follow active locale (`zhCN` / `enUS`) when a migrated component uses them.
|
||||
|
||||
## 11. Error Handling & Fallbacks
|
||||
|
||||
| Case | Behavior |
|
||||
| --- | --- |
|
||||
| Missing message key | Dev warning; do not crash; show key or fallback language value |
|
||||
| Unsupported cookie locale | Normalize to supported locale / default |
|
||||
| Partial migration | Keep hard-coded Chinese on unmigrated screens |
|
||||
| Backend error strings | Display raw `error_msg` |
|
||||
|
||||
## 12. Testing & Acceptance
|
||||
|
||||
Manual:
|
||||
|
||||
1. No cookie + browser Chinese → Chinese UI
|
||||
2. No cookie + browser English → English UI
|
||||
3. Manual switch to English survives refresh
|
||||
4. Manual switch back to Chinese survives refresh
|
||||
5. Core paths (layout/auth/settings) have no major residual hard-coded Chinese UI copy
|
||||
6. `pnpm build` and `pnpm build:embed` both succeed
|
||||
7. Language switch does not break theme, session, or sidebar state
|
||||
|
||||
Automated (recommended):
|
||||
|
||||
- Unit tests for `normalizeLocale` / resolution priority
|
||||
- Script or test asserting `zh-CN.json` and `en.json` key parity
|
||||
|
||||
## 13. Rollout Plan (high level)
|
||||
|
||||
1. Add i18n infrastructure and empty/core message files
|
||||
2. Mount provider and language switcher
|
||||
3. Migrate layout shell copy
|
||||
4. Migrate auth copy
|
||||
5. Migrate settings copy + appearance language control
|
||||
6. Verify SSR and static-export builds
|
||||
7. Document how later pages should adopt `useTranslations`
|
||||
|
||||
## 14. Open Implementation Notes
|
||||
|
||||
- Prefer cookie name `NEXT_LOCALE` unless an existing project cookie convention conflicts during implementation
|
||||
- Prefer minimal surface-area integration with next-intl; avoid introducing locale-based routing APIs that break static export
|
||||
- Keep `internal/util` and backend packages untouched
|
||||
- After implementation, follow repo frontend conventions and existing provider composition style
|
||||
@@ -1,268 +0,0 @@
|
||||
# Message Gateway Design (Wavelet)
|
||||
|
||||
**Date:** 2026-08-16
|
||||
**Status:** Approved for implementation planning
|
||||
**Scope:** Wavelet framework only (sub-project 1 of 3)
|
||||
**Product:** Wavelet scaffold — reusable inbound/outbound messaging channels
|
||||
|
||||
## 1. Goal
|
||||
|
||||
Give Wavelet a Hermes-style **message gateway**: admins configure Telegram and QQ bots in the admin UI; end users bind their private-chat identity with a one-time short code on the profile page. Inbound private messages become **domain events**. Products (Clipper later) subscribe and decide what to persist.
|
||||
|
||||
This spec does **not** write Clipper `c_items`. That is sub-project 3 after Wavelet is merged into Clipper.
|
||||
|
||||
### In scope (Wavelet)
|
||||
|
||||
- `pkg/message_gateway` + `channel/telegram` + `channel/qq`
|
||||
- Admin UI `/admin/message-gateway` (empty state, add channel, per-type forms, cards)
|
||||
- User profile card: bind / list / unbind
|
||||
- Worker-hosted connections (Telegram long poll, QQ official WebSocket)
|
||||
- Pairing codes, bindings, encrypted credentials
|
||||
- Domain event on authorized inbound messages
|
||||
- Text + image + file inbound/outbound at the adapter layer (v1 private chat only)
|
||||
|
||||
### Out of scope
|
||||
|
||||
- Group / guild / channel / @mention routing
|
||||
- Hermes sessions, slash commands, STT, cron, circuit-breaker fleet, 20 platforms
|
||||
- Telegram webhook mode (long poll only)
|
||||
- Writing business tables (`c_*`) or calling `upload.Ingest` from `pkg/`
|
||||
- Changing `pkg/push` (outbound notification remains a separate system)
|
||||
- Multi-tenant hosted bots (`owner_scope=user`) — column exists, v1 only inserts `system`
|
||||
|
||||
### Later sub-projects (not this spec)
|
||||
|
||||
2. Clipper `git fetch upstream && git merge upstream/main`
|
||||
3. Clipper listener: inbound event → `upload.Ingest` + `c_items` with `source=telegram|qq`
|
||||
|
||||
## 2. Decisions (locked)
|
||||
|
||||
| Topic | Choice |
|
||||
|---|---|
|
||||
| Ownership | Instance-level shared bots; users pair to them |
|
||||
| Pairing | 8-char one-time code, 15 minutes, consumed on success |
|
||||
| Profile | Show bound platform user ID; user can unbind |
|
||||
| Surface | Private chat / C2C only; groups ignored + logged |
|
||||
| Runtime | Worker process starts the gateway; API does not poll |
|
||||
| Events | Bound inbound → `internal/listener` domain event |
|
||||
| Telegram SDK | `gopkg.in/telebot.v4` (tucnak/telebot current module) |
|
||||
| QQ SDK | Official QQ Bot Go SDK (`botgo` / [docs](https://bot.q.qq.com/wiki/develop/gosdk/)) |
|
||||
| Hermes | Adapter shape only (`Connect`/`Disconnect`/`Send`/inbound event/capabilities) |
|
||||
|
||||
## 3. Architecture
|
||||
|
||||
```
|
||||
Admin UI ──CRUD──► API ──w_message_channels──► Worker Gateway
|
||||
│
|
||||
User DM ──telebot / botgo──► Channel adapter ─────────┤
|
||||
▼
|
||||
unbound? mint pairing code, reply
|
||||
bound? emit InboundMessage event
|
||||
│
|
||||
User profile ──bind/unbind──► API ──w_message_bindings / pairing_codes
|
||||
│
|
||||
product listener (Clipper later)
|
||||
```
|
||||
|
||||
`pkg/message_gateway` has **no** Gin, GORM, sessions, or `internal/apps` imports. Persistence lives in `internal/repository`. Encryption of credentials uses existing Wavelet secret helpers from apps/repository, not from `pkg`.
|
||||
|
||||
### 3.1 Packages
|
||||
|
||||
```
|
||||
pkg/message_gateway/
|
||||
types.go # Message, Attachment, Capability, ChannelType
|
||||
channel.go # Channel interface
|
||||
registry.go # Register / Lookup
|
||||
pairing.go # GenerateCode (alphabet, length) — no storage
|
||||
channel/telegram/ # telebot private chat
|
||||
channel/qq/ # official botgo C2C
|
||||
internal/apps/admin/message_gateway/
|
||||
internal/apps/message_gateway/ # user bind/unbind
|
||||
internal/listener/ # InboundMessage event type + dispatch
|
||||
internal/model/ # channel, binding, pairing row types
|
||||
internal/repository/ # CRUD
|
||||
```
|
||||
|
||||
Frontend:
|
||||
|
||||
```
|
||||
frontend/lib/services/message-gateway/
|
||||
frontend/app/(main)/admin/message-gateway/
|
||||
page.tsx
|
||||
components/channel-card.tsx
|
||||
components/add-channel-dialog.tsx
|
||||
channels/telegram/form.tsx
|
||||
channels/qq/form.tsx
|
||||
frontend/components/common/settings/ # profile bind card
|
||||
```
|
||||
|
||||
Sidebar admin item: `titleKey: 'messageGateway'`, url `/admin/message-gateway`, placed next to push.
|
||||
|
||||
### 3.2 Channel interface
|
||||
|
||||
```go
|
||||
type Channel interface {
|
||||
Type() string
|
||||
Connect(ctx context.Context) error
|
||||
Disconnect(ctx context.Context) error
|
||||
Send(ctx context.Context, to Recipient, msg OutboundMessage) error
|
||||
Capabilities() Capability
|
||||
}
|
||||
|
||||
type Capability struct {
|
||||
Text, Image, File, Reply bool
|
||||
Group bool // always false in v1 adapters
|
||||
}
|
||||
|
||||
type InboundMessage struct {
|
||||
ChannelID uint64
|
||||
ChannelType string
|
||||
PlatformUserID string
|
||||
ChatID string
|
||||
MessageID string
|
||||
Text string
|
||||
Attachments []Attachment // local temp paths + mime; no upload package
|
||||
BindingUserID *uint64 // nil if unbound
|
||||
}
|
||||
|
||||
type Handler func(ctx context.Context, msg InboundMessage) error
|
||||
```
|
||||
|
||||
Gateway runner (Worker) constructs adapters from DB rows, calls `Connect`, and registers one `Handler` that implements pairing + event emit.
|
||||
|
||||
### 3.3 Process model
|
||||
|
||||
- `internal/cmd` Worker path: after bootstrap, `messagegateway.Start(ctx)`.
|
||||
- API process never starts adapters.
|
||||
- Enable / disable / credential change: Worker watches DB (poll every few seconds or Redis pub/sub already used by the platform). v1 may poll `updated_at` every 5s. Hot-reload only the changed channel.
|
||||
- Single Worker assumed. If a second Worker starts, it tries a Redis/DB lock keyed by `channel_id` + token fingerprint; failure → skip that channel and log.
|
||||
- Per-channel reconnect with exponential backoff; one channel crash must not stop others.
|
||||
|
||||
## 4. Data model
|
||||
|
||||
All tables `w_*`. Dual goose SQL (Postgres + SQLite). No physical FKs.
|
||||
|
||||
### 4.1 `w_message_channels`
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| id | snowflake | PK |
|
||||
| name | string | admin display name |
|
||||
| type | string | `telegram` \| `qq` |
|
||||
| owner_scope | string | v1 always `system` |
|
||||
| owner_id | bigint null | reserved; null in v1 |
|
||||
| enabled | bool | |
|
||||
| credentials | text | encrypted JSON; never returned raw |
|
||||
| extra | text | optional JSON (base URL, sandbox host) |
|
||||
| created_at, updated_at | timestamptz | |
|
||||
|
||||
### 4.2 `w_message_bindings`
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| id | snowflake | PK |
|
||||
| user_id | bigint | Wavelet user |
|
||||
| channel_id | bigint | |
|
||||
| platform_user_id | string | Telegram user id / QQ OpenID |
|
||||
| created_at | timestamptz | |
|
||||
|
||||
Unique `(channel_id, platform_user_id)`. A user may bind many channels; a platform identity binds to at most one user per channel.
|
||||
|
||||
### 4.3 `w_message_pairing_codes`
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| code | string | PK, 8 chars |
|
||||
| channel_id | bigint | |
|
||||
| platform_user_id | string | |
|
||||
| expires_at | timestamptz | now + 15m |
|
||||
| created_at | timestamptz | |
|
||||
|
||||
Delete row on successful bind. Expired rows ignored and cleaned by a small periodic delete (may live in the Worker loop).
|
||||
|
||||
**Code alphabet:** `ABCDEFGHJKLMNPQRSTUVWXYZ23456789` (no `0O1I`). Format displayed as `XXXX-XXXX`.
|
||||
|
||||
## 5. Pairing and profile
|
||||
|
||||
1. Unbound private message arrives.
|
||||
2. Worker upserts a pairing row (if an unexpired code already exists for that pair, reuse it).
|
||||
3. Bot replies with the code and “Settings → Profile → Bind a bot”.
|
||||
4. User opens profile card, picks an **enabled** channel, submits the code.
|
||||
5. API validates, inserts binding, deletes the code.
|
||||
6. Profile lists bindings: channel name, type, **platform user ID**, unbind button.
|
||||
|
||||
Unauthorized/unbound never emit `InboundMessage` to product listeners.
|
||||
|
||||
## 6. Admin UI and credentials
|
||||
|
||||
Empty state + “Add channel”. Dialog: choose `telegram` | `qq`, then that type’s form.
|
||||
|
||||
| Type | Required | Optional |
|
||||
|---|---|---|
|
||||
| telegram | name, bot token | API base URL (default `https://api.telegram.org`) |
|
||||
| qq | name, App ID, App Secret | sandbox / portal host (default `q.qq.com`) |
|
||||
|
||||
Cards: name, type, enabled toggle, connected/disconnected (best-effort from Worker), edit, delete. Edit never echoes raw secrets; empty secret field means “keep current”.
|
||||
|
||||
Create: validate field shape, optional SDK probe (`getMe` / token), then insert. Probe failure → 400, no row.
|
||||
|
||||
## 7. HTTP API
|
||||
|
||||
Admin (admin middleware), prefix `/api/v1/admin/message-gateway`:
|
||||
|
||||
- `GET /channels` — list (secrets masked)
|
||||
- `GET /channels/definitions` — form schema per type
|
||||
- `POST /channels` — create
|
||||
- `PATCH /channels/:id` — update
|
||||
- `DELETE /channels/:id` — delete (cascade bindings + pairing rows in logics)
|
||||
- `POST /channels/:id/test` — optional probe
|
||||
|
||||
User (login required), prefix `/api/v1/message-gateway`:
|
||||
|
||||
- `GET /bindings` — current user’s bindings
|
||||
- `POST /bindings` — `{ channel_id, code }`
|
||||
- `DELETE /bindings/:id` — unbind own row only
|
||||
|
||||
Errors via `response.Abort*`. Invalid/expired code → 400. Platform identity already bound → 409.
|
||||
|
||||
## 8. Inbound media and reply
|
||||
|
||||
Adapters accept private-chat **text, images, files**. Voice/STT is out of scope; a voice message is treated as a file attachment if the SDK delivers bytes, otherwise ignored with a log.
|
||||
|
||||
Attachments are written under `t.TempDir()`-equivalent process temp (`os.MkdirTemp`) and passed as filesystem paths on `InboundMessage`. The product listener (Clipper later) must `upload.Ingest` and then delete the temp file. Gateway deletes leftovers older than 1 hour.
|
||||
|
||||
If the product handler returns an error, the bot sends a generic “could not save your message” (no internal error text). Success ACK is a single short reply when `Capabilities().Reply` is true. ACK can be disabled later via extra JSON; v1 always ACKs.
|
||||
|
||||
Group / non-C2C updates: log and drop. Do not mint pairing codes.
|
||||
|
||||
## 9. Domain event
|
||||
|
||||
Name: `message_gateway.inbound` (exact string in `internal/listener`).
|
||||
|
||||
Payload: `InboundMessage` plus `BindingUserID` set. Register in `internal/platform/bootstrap` (no `init()`). Wavelet ships a no-op or log-only listener. Clipper will register the clip writer in sub-project 3.
|
||||
|
||||
## 10. Error handling
|
||||
|
||||
- Adapter panics: recover in the gateway runner, mark channel disconnected, backoff reconnect.
|
||||
- Media download failure: keep text; attachment entry has `Error` string; still emit if bound.
|
||||
- Encrypt/decrypt failure: treat channel as disabled, log, do not start adapter.
|
||||
- i18n: all new UI strings in `zh-CN.json` + `en.json` (`admin.messageGateway`, `settings.botBinding`).
|
||||
|
||||
## 11. Testing and done criteria
|
||||
|
||||
| Gate | Pass |
|
||||
|---|---|
|
||||
| Fake channel + pairing | generate, reuse unexpired, expire, consume, conflict 409 |
|
||||
| Telegram adapter unit | mock telebot updates: text, photo, document; groups dropped |
|
||||
| QQ adapter unit | mock C2C event; non-C2C dropped |
|
||||
| API tests | admin CRUD, bind/unbind, bad code |
|
||||
| `go test ./...` | pass |
|
||||
| Frontend | `pnpm tsc --noEmit --jsx preserve`, `check-i18n-keys.mjs` |
|
||||
| Manual | add TG channel → DM gets code → profile bind → second DM emits event in logs |
|
||||
|
||||
## 12. Risks
|
||||
|
||||
- **Official QQ Bot** requires a published/sandbox app at q.qq.com; local dev may only test Telegram.
|
||||
- **telebot v4 module path** is `gopkg.in/telebot.v4`; pin a version in `go.mod`.
|
||||
- **One poller per token:** documented; second Worker skips the channel.
|
||||
- **Token leak in logs:** never log raw credentials or pairing codes at info level (debug only, redacted).
|
||||
Reference in New Issue
Block a user