feat: 新增记账模块与 API Key 管理 — 支持收支记录、账户管理、N8N 外部集成

This commit is contained in:
2026-04-01 02:45:13 +08:00
parent dcccf6675f
commit 41f21ec3a9
32 changed files with 3896 additions and 16 deletions
+67 -3
View File
@@ -18,6 +18,7 @@ TanStack Start fullstack web app with ORPC (contract-first RPC) and shadcn/ui.
- **Hotkeys**: @tanstack/react-hotkeys (`useHotkey` for type-safe keyboard shortcuts)
- **Animation**: motion (page transitions, staggered entrance, layout animation)
- **Command Palette**: cmdk (via shadcn Command component, triggered by ⌘K)
- **API Key**: @better-auth/api-key (API key auth for external integrations like N8N)
- **Build**: Vite + Nitro
## Architecture Overview
@@ -27,6 +28,8 @@ TanStack Start fullstack web app with ORPC (contract-first RPC) and shadcn/ui.
```
/ → Dashboard overview (greeting, quick bookmarks, module summary)
/bookmarks → Bookmarks page (view mode + edit mode toggle)
/finance → Finance page (transaction list with filters, account/category management)
/settings → Settings page (API key management)
/setup → One-time owner setup (first visit only, redirects to /login after)
/login → Login page (redirects to /setup if no owner exists)
```
@@ -56,7 +59,7 @@ src/
├── client/
│ └── orpc.ts # ORPC client (isomorphic: SSR direct call / CSR fetch)
├── components/
│ ├── AppSidebar.tsx # Unified sidebar (reads module registry, collapsible)
│ ├── AppSidebar.tsx # Unified sidebar (reads module registry, collapsible, includes Settings link in footer)
│ ├── CommandPalette.tsx # ⌘K command palette (search bookmarks, engines, navigation)
│ ├── Error.tsx # Error boundary fallback
│ ├── NotFound.tsx # 404 fallback
@@ -67,7 +70,19 @@ src/
│ └── utils.ts # cn() utility
├── modules/
│ ├── registry.ts # ModuleMetadata interface + modules[]
── bookmarks/ # Bookmarks module (schema, contract, router, components/)
── bookmarks/ # Bookmarks module (schema, contract, router, components/)
│ └── finance/ # Finance module
│ ├── index.ts # Module metadata
│ ├── schema.ts # Drizzle tables (financeAccount, transactionCategory, transaction)
│ ├── contract.ts # ORPC contracts (account, category, transaction CRUD + summary)
│ ├── router.ts # ORPC handlers
│ └── components/ # React UI components
│ ├── AccountFormDialog.tsx
│ ├── AccountManager.tsx
│ ├── CategoryFormDialog.tsx
│ ├── CategoryManager.tsx
│ ├── TransactionFormDialog.tsx
│ └── TransactionList.tsx
├── cli/
│ ├── index.ts # citty CLI entrypoint (bun run cli ...)
│ └── commands/auth.ts # auth reset-password command
@@ -76,7 +91,9 @@ src/
│ ├── _protected.tsx # Auth guard + unified shell (SidebarProvider, AppSidebar, CommandPalette)
│ ├── _protected/
│ │ ├── index.tsx # Dashboard overview (greeting, quick bookmarks, module cards)
│ │ ── bookmarks.tsx # Bookmarks page (view/edit toggle, Motion animations)
│ │ ── bookmarks.tsx # Bookmarks page (view/edit toggle, Motion animations)
│ │ ├── finance.tsx # Finance page (transaction list, filters, account/category management)
│ │ └── settings.tsx # Settings page (API key management)
│ ├── login.tsx, setup.tsx
│ └── api/ # $.ts (OpenAPI), auth.$.ts, health.ts, rpc.$.ts
├── server/
@@ -340,6 +357,49 @@ Kairos is a self-hosted single-user app. There is NO public registration. The fi
- **ORPC middleware**: `authMiddleware` calls `auth.api.getSession({ headers })` → injects `context.user`
- **Password reset**: Via server CLI only (`bun run cli auth reset-password`) — no web-based password recovery
### API Key Authentication
- **Plugin**: `@better-auth/api-key` with `enableSessionForAPIKeys: true`
- **Auth middleware**: Unchanged — API keys automatically simulate sessions via Better Auth plugin
- **Header**: `x-api-key: kairos_xxxxxxxx` (or `Authorization: Bearer kairos_xxxxxxxx`)
- **Management**: Via `/settings` page UI (create, list, delete)
- **Use case**: N8N workflow automation → parse emails → call ORPC API to create transactions
- **Default prefix**: `kairos_`
## Finance Module
### Data Model
- **financeAccount**: Bank accounts, wallets, credit cards. Fields: name, type (checking/savings/credit/cash/investment/loan), currencyCode (ISO 4217, default CNY), initialBalance (integer, smallest currency unit), icon, isArchived, orderId
- **transactionCategory**: Flat expense/income categories. Fields: name, icon, type (expense/income), orderId
- **transaction**: Financial records. Fields: accountId, categoryId (nullable), type (expense/income), amount (integer, positive, smallest currency unit), description, note, date, source (manual/n8n/import), externalId (unique, for N8N dedup)
### Amount Convention
- Stored as **integer in smallest currency unit** (e.g., ¥120.30 = 12030)
- Always **positive** — type field determines income vs expense
- Display: `(amount / 100).toFixed(2)` with currency symbol
### N8N Integration
External services call ORPC API with API key:
```bash
curl -X POST https://kairos.example/api/rpc/finance.transaction.create \
-H "x-api-key: kairos_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"accountId":"...","type":"expense","amount":3500,"description":"午餐","date":"2026-04-01T12:30:00Z","source":"n8n","externalId":"email-abc123"}'
```
- `externalId` enables idempotent deduplication — duplicate imports return existing transaction
- `source: "n8n"` marks automated imports
### ORPC Endpoints
- `finance.account.list/create/update/remove/reorder`
- `finance.category.list/create/update/remove/reorder`
- `finance.transaction.list/create/update/remove/summary`
## Critical Rules
**DO:**
@@ -367,3 +427,7 @@ Kairos is a self-hosted single-user app. There is NO public registration. The fi
- Use `@/*` aliases in Drizzle schema files (drizzle-kit can't resolve them)
- Add registration/signup functionality (single-owner model, enforced by `databaseHooks`)
- Create separate admin pages — integrate view/edit modes in each module page
- Use `authClient.apiKey` methods for API key management (not ORPC)
- Store amount as float/decimal — amounts MUST be integer (smallest currency unit)
- Duplicate `externalId` on transactions — must be unique (for N8N dedup)
- Try to retrieve API key after creation — keys are shown only once