Files
workspace/code/app/CLAUDE.md
T
2026-06-27 16:52:47 +08:00

150 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Allapp is a family/space mini-program platform — a main shell with sub-apps inside it. The frontend owns most business logic; the backend is deliberately thin and generic (auth, file storage, ID generation, database access).
- **Backend**: `app-go/` — Go 1.25, Fiber v3.1, pgx (PostgreSQL), JWT, Viper, Zap
- **Frontend**: `app-rn/` — React Native 0.85, Expo 56, Expo Router, Zustand, Alova, HeroUI Native, Uniwind (Tailwind CSS)
- **Database**: PostgreSQL 18.1, database `allapp`, schema `public`
Read `PROJECT_CONTEXT.md` for deeper context; this file focuses on what you need day-to-day.
## Commands
### Backend (`app-go/`)
```bash
cd app-go
go build ./cmd/app # Build
go run ./cmd/app # Run server
go test ./... # Run all tests
go mod tidy # Tidy dependencies
```
### Frontend (`app-rn/`)
```bash
cd app-rn
pnpm start # Expo dev server (port 8083)
pnpm android # Android build
pnpm ios # iOS build
pnpm lint # oxlint
pnpm exec tsc --noEmit # TypeScript type check
pnpm exec oxfmt # Format code
```
WSL-specific: if `node`/`pnpm` are not found, run:
```bash
export PATH="$HOME/.local/share/fnm:$PATH"
eval "$(fnm env --shell bash)"
```
## Architecture
### Bootstrap Flow
```
Backend: config → logger → S3 → Postgres → db.Init → unique ID → JWT → WeChat → Fiber server
Frontend: /index → token check → auth/login or family/entry or /home
```
### Backend Design
**All CRUD goes through 5 generic data APIs.** Never create dedicated REST endpoints for feature-specific operations:
| Endpoint | Purpose |
|---|---|
| `POST /app/data/loadData` | Query by table/view name, conditions, columns, order, args |
| `POST /app/data/loadDataPage` | Paginated query — same as loadData + `page`/`page_size`, returns `{rows, total, page, page_size}` |
| `POST /app/data/loadDataBySql` | Raw SQL query |
| `POST /app/data/saveData` | Batch insert/update/delete by table name and key field |
| `POST /app/data/getUniqueId` | Get snowflake IDs (1–100) |
Auth endpoints: `/app/auth/login/{qq,wechat,test}`, `/app/bind/{qq,wechat}`.
**File endpoints:**
| Endpoint | Purpose |
|---|---|
| `POST /app/s3/upload` | Upload files (multipart form, field name `files`), stored under `YYYY/MM/DD/{uuid}.ext`, returns `string[]` keys |
| `POST /app/s3/delete` | Delete files by keys, body `{keys: string[]}`, failure is logged but still returns success |
On the frontend: `s3UploadFileApi(files[])` wraps files into a `FormData` with key `"files"` and `multipart/form-data` content type. For deleting uploaded files, use `deleteS3FileQuietly()` from `app-rn/src/utils/file.ts`; do not reimplement S3 key checks or silent delete/catch logic in pages or components.
Auth token is passed in a custom `Token` header. JWT middleware stores `user_id` in context. Audit fields (`create_time`, `update_time`, `create_by`, `update_by`) are auto-injected by `pkg/db/meta_field.go` for all tables except `b_user` and `b_user_oauth`.
### Frontend Design
Expo Router file-based routing under `src/app/`:
- `(main)/(tabs)/` — main shell tabs (home, chat, my)
- `(main)/auth/`, `(main)/family/`, `(main)/tools/`, `(main)/activities/`
- `finance/(tabs)/` — finance sub-app (independent from main)
- Layouts: `AppLayout` for main shell, `ModuleLayout` for sub-app shells, `PageLayout` for simple pages
State: Zustand with `persist` middleware for `user` and `family` (AsyncStorage). Finance store is in-memory only.
API layer: single Alova instance (`src/request/index.ts`), API wrappers in `src/request/api.ts`. Key wrappers: `loadDataApi`, `loadDataPageApi` (paginated, returns `PageData<T>` with `rows`/`total`/`page`/`page_size`), `loadDataBySqlApi`, `saveDataApi`, `getUniqueIdApi`. Response codes: 1000 (success), 1001 (unauthorized → redirect to login), 2000 (business error), 3000 (system error).
Startup flow in `src/app/index.tsx`:
```
no token → /auth/login
token invalid → /auth/login
no family → /family/entry
has family → /home
```
## Critical Conventions
### 1. No New Dedicated API Endpoints
Always reuse the 5 generic data APIs. If you think a new endpoint is needed, **ask the user first**. Even then, new endpoints must be generic helpers, not feature-specific.
### 2. Database Tables Must Include Audit Columns
All new business tables must include: `id BIGINT PRIMARY KEY` (snowflake), `create_time TIMESTAMPTZ`, `update_time TIMESTAMPTZ`, `create_by BIGINT`, `update_by BIGINT`. Do not add new tables to `auditExcludeTables` — only `b_user` and `b_user_oauth` are exempt.
### 3. Family/Space-Centric Data Model
All business data (finance, favorites, etc.) associates with `family_id`. Users link to families via `b_family_member`. Always filter queries by the current user's `family_id`.
### 4. Frontend Family-Scoped Data Access
On the frontend, keep using the generic data APIs in `app-rn/src/request/api.ts`. Pages are responsible for passing the right family/user scope in `search_condition`, `args`, and inserted rows. Use `useFamilyStore((s) => s.getFamilyId())` for the current family id and `useUserStore((s) => s.getUserId())` for the current user id.
### 5. Family Feed Recording
Certain actions must call `recordFeed()` from `@/helpers/feed`. Use `{nickname}` placeholder in content — do NOT hardcode nicknames. The `loadFeeds()` helper replaces it dynamically. Scenarios: creating/joining family (`join`), renaming (`rename`), recording bills (`bill`), favoriting apps (`fav`).
### 6. Frontend: Use Existing Libraries Only
Before writing any page/component, check `app-rn/package.json` for available dependencies. Use HeroUI Native for UI, `@legendapp/list` for long lists, `lucide-react-native` for icons, `sonner-native` for toasts. Use NativeWind `className` for styling, not inline `style={}`.
### 7. Loading State: Always Use useLoading + LoadingOverlay
Any async save/update/delete operation must use the `useLoading` hook + `<LoadingOverlay>` pattern — never roll per-field loading booleans. The hook provides `startLoading(message)` (shows toast.loading + page mask), `stopLoading(options?)` (dismisses toast, optionally shows success/error), and `isLoading` (boolean for disabling buttons). Place `<LoadingOverlay visible={isLoading} />` near the bottom of the page JSX (before closing `</PageLayout>`). Reference: `src/hooks/useLoading.ts`, `src/components/LoadingOverlay.tsx`.
### 8. Finance Convention
`type = 0` means expense, `type = 1` means income. Category icon names match iconfont names (e.g., `finance_jiaotong`, `finance_canyin`).
## Key Files
| File | Role |
|---|---|
| `PROJECT_CONTEXT.md` | Full project documentation, risks, feature status |
| `app-go/internal/bootstrap/app.go` | Backend initialization flow |
| `app-go/internal/router/router.go` | Route definitions |
| `app-go/pkg/db/meta_field.go` | Audit field auto-injection |
| `app-rn/src/app/index.tsx` | Startup/auth routing logic |
| `app-rn/src/request/index.ts` | Alova instance, auth interceptor |
| `app-rn/src/request/api.ts` | API wrapper functions |
| `app-rn/src/store/` | Zustand stores (user, family, finance, app, favorite) |
| `app-rn/src/layouts/` | Shell layouts (AppLayout, ModuleLayout, PageLayout) |
| `app-rn/src/configs/pages.ts` | Sub-app definitions |
| `app-rn/src/hooks/useLoading.ts` | Loading state hook (toast + overlay) |
| `app-rn/src/components/LoadingOverlay.tsx` | Full-page loading mask component |
| `app-rn/src/helpers/feed.ts` | Family feed recording |
## Known Risks
- Generic data APIs accept table names and raw SQL from the frontend — needs server-side whitelisting before production.
- `db.local.yml` contains database credentials and should never be committed.
- Finance category queries currently don't filter by `family_id`.
- Main tab `/space` route is missing its page file.
- `GetUniqueIdReq.Count` uses `binding` tag instead of `validate` tag — min/max rules may not run.