480 lines
13 KiB
Markdown
480 lines
13 KiB
Markdown
# Allapp Project Context
|
||
|
||
This file is a compact project memory for future Codex chats. Read it first when working in
|
||
`D:\workspace\code\app`.
|
||
|
||
## Workspace
|
||
|
||
- Root: `D:\workspace\code\app`
|
||
- Git repository root: `D:\workspace`
|
||
- Backend: `app-go`
|
||
- Frontend: `app-rn`
|
||
- Local database memo: `db.local.yml`
|
||
- Treat config files as sensitive. Some local config files may contain real credentials.
|
||
|
||
## Product Mental Model
|
||
|
||
Allapp is shaped more like a mini-program platform than a single linear mobile app.
|
||
|
||
There is a main shell for the family/personal space, and feature modules behave like small apps
|
||
inside it. The frontend owns most product flow and business behavior. The backend is deliberately
|
||
thin and generic, acting mostly as auth, file storage, ID generation, and database access.
|
||
|
||
High-level model:
|
||
|
||
```text
|
||
Allapp main shell
|
||
├── Home / Chat / Tools / My
|
||
├── Auth and family/space management
|
||
└── Sub-apps
|
||
├── Finance
|
||
│ ├── Home
|
||
│ ├── Add record
|
||
│ └── Analysis
|
||
└── Clothes matching
|
||
└── Configured in pages.ts, route not implemented yet
|
||
```
|
||
|
||
Important naming note:
|
||
|
||
- The product often uses "family" and "space" as the same concept.
|
||
- Database tables use `b_family` and `b_family_member`.
|
||
- Main tab config currently includes `/space`, but there is no matching page yet.
|
||
|
||
## Backend: app-go
|
||
|
||
Tech stack:
|
||
|
||
- Go 1.25
|
||
- Fiber v3.1
|
||
- pgx PostgreSQL client
|
||
- JWT
|
||
- Viper config
|
||
- Zap logging
|
||
- S3/RustFS file storage
|
||
- QQ/WeChat login and binding
|
||
- Snowflake-style unique ID generator
|
||
|
||
Important files:
|
||
|
||
- Entry: `app-go/cmd/app/main.go`
|
||
- Bootstrap: `app-go/internal/bootstrap/app.go`
|
||
- Server: `app-go/internal/bootstrap/server.go`
|
||
- Routes: `app-go/internal/router/router.go`
|
||
- Config type: `app-go/internal/config/config.go`
|
||
- Request binding/validation: `app-go/internal/httpx/request.go`
|
||
- Response shape: `app-go/internal/httpx/response.go`
|
||
- Auth middleware: `app-go/internal/middleware/auth.go`
|
||
- Generic DB access: `app-go/pkg/db/*`
|
||
|
||
Bootstrap flow:
|
||
|
||
```text
|
||
config -> logger -> S3 client -> Postgres pool -> db.Init
|
||
-> unique ID -> JWT -> WeChat client -> Fiber server
|
||
```
|
||
|
||
API base path comes from config and is currently:
|
||
|
||
```text
|
||
/app
|
||
```
|
||
|
||
Important endpoints:
|
||
|
||
```text
|
||
POST /app/auth/login/qq
|
||
POST /app/auth/login/wechat
|
||
POST /app/auth/login/test
|
||
POST /app/bind/qq
|
||
POST /app/bind/wechat
|
||
|
||
POST /app/data/loadData
|
||
POST /app/data/loadDataBySql
|
||
POST /app/data/saveData
|
||
POST /app/data/getUniqueId
|
||
|
||
POST /app/s3/upload
|
||
POST /app/s3/delete
|
||
```
|
||
|
||
Backend architecture notes:
|
||
|
||
- `loadData` accepts a table/view name, selected columns, condition string, order string, and args.
|
||
- `loadDataBySql` accepts raw SQL and args.
|
||
- `saveData` accepts batches of inserts, updates, and deletes by table name/key field.
|
||
- Insert/update helpers add audit fields for most tables via `applyMetaFields`.
|
||
- `Token` header is parsed by `middleware.Auth()` and stores `user_id` in context.
|
||
- There are no explicit database foreign keys in the inspected schema; relationships are enforced by code and naming conventions.
|
||
|
||
Security/architecture caution:
|
||
|
||
- The generic data API is very flexible but risky.
|
||
- Table names, columns, conditions, ordering, and raw SQL can come from the frontend.
|
||
- If this moves toward production, prioritize server-side table/field whitelists, ownership checks, and removing or restricting raw SQL.
|
||
|
||
Validation notes:
|
||
|
||
- `BindAndValidate` validates structs with `go-playground/validator`.
|
||
- It skips validation for slices.
|
||
- `GetUniqueIdReq.Count` currently uses a `binding` tag, not a `validate` tag, so its min/max rules may not run.
|
||
|
||
## Frontend: app-rn
|
||
|
||
Tech stack:
|
||
|
||
- Expo 56
|
||
- React Native 0.85
|
||
- React 19
|
||
- Expo Router
|
||
- Zustand
|
||
- Alova with Axios adapter
|
||
- HeroUI Native
|
||
- Uniwind / Tailwind-style classes
|
||
- Lucide React Native icons
|
||
- QQ/WeChat native login plugins
|
||
|
||
Important files:
|
||
|
||
- Package: `app-rn/package.json`
|
||
- App config: `app-rn/app.json`
|
||
- Root layout: `app-rn/src/app/_layout.tsx`
|
||
- Startup redirect page: `app-rn/src/app/index.tsx`
|
||
- Request wrapper: `app-rn/src/request/index.ts`
|
||
- API methods: `app-rn/src/request/api.ts`
|
||
- User store: `app-rn/src/store/user.ts`
|
||
- Family store: `app-rn/src/store/family.ts`
|
||
- Finance store: `app-rn/src/store/finance.ts`
|
||
- Main shell layout: `app-rn/src/layouts/AppLayout.tsx`
|
||
- Module shell layout: `app-rn/src/layouts/ModuleLayout.tsx`
|
||
- Shared tabbar: `app-rn/src/components/layout/tabbar.tsx`
|
||
- App/module list config: `app-rn/src/configs/pages.ts`
|
||
|
||
Frontend/backend contract:
|
||
|
||
- Frontend reads `EXPO_PUBLIC_API_URL`.
|
||
- Requests use Alova with Axios adapter.
|
||
- Auth token is sent in a custom `Token` header.
|
||
- Login APIs are whitelisted in the request wrapper.
|
||
- Backend response shape:
|
||
|
||
```ts
|
||
{
|
||
code: number;
|
||
message: string;
|
||
data: unknown;
|
||
track_id?: string;
|
||
}
|
||
```
|
||
|
||
Response codes:
|
||
|
||
```text
|
||
1000 success
|
||
1001 unauthorized/token expired
|
||
2000 business error
|
||
3000 system error
|
||
```
|
||
|
||
State:
|
||
|
||
- `user-storage`: persisted user token/id/nickname/avatar.
|
||
- `family-storage`: persisted current family id.
|
||
- Finance categories are in memory only and loaded by the finance module layout.
|
||
|
||
Startup flow:
|
||
|
||
```text
|
||
/ index
|
||
├── no local token -> /auth/login
|
||
├── token exists but user check fails -> /auth/login
|
||
├── token exists but no family -> /family/entry
|
||
└── token exists and family exists -> /home
|
||
```
|
||
|
||
## Frontend Route Structure
|
||
|
||
Expo Router tree:
|
||
|
||
```text
|
||
src/app
|
||
├── _layout.tsx
|
||
├── index.tsx
|
||
├── +not-found.tsx
|
||
├── (main)/
|
||
│ ├── (tabs)/
|
||
│ │ ├── _layout.tsx
|
||
│ │ ├── home/index.tsx
|
||
│ │ ├── chat/index.tsx
|
||
│ │ └── my/index.tsx
|
||
│ ├── activities/
|
||
│ │ └── index.tsx
|
||
│ ├── auth/
|
||
│ │ └── login.tsx
|
||
│ ├── family/
|
||
│ │ ├── entry.tsx
|
||
│ │ ├── settings.tsx
|
||
│ │ ├── user.tsx
|
||
│ │ └── member/[id].tsx
|
||
│ └── tools/
|
||
│ └── index.tsx
|
||
└── finance/
|
||
├── (tabs)/
|
||
│ ├── _layout.tsx
|
||
│ ├── home/index.tsx
|
||
│ └── analysis/index.tsx
|
||
├── add/index.tsx
|
||
└── sub/index.tsx
|
||
```
|
||
|
||
Effective routes:
|
||
|
||
```text
|
||
/ startup redirect
|
||
/auth/login login
|
||
/home main home
|
||
/chat chat/image picker demo
|
||
/my profile tab
|
||
/activities family activity feed
|
||
/tools app/module list
|
||
/family/entry create or join family
|
||
/family/settings family settings
|
||
/family/user user profile settings
|
||
/family/member/:id family member detail
|
||
/finance/home finance module home
|
||
/finance/add add finance record
|
||
/finance/analysis finance analysis
|
||
/finance/sub test page
|
||
```
|
||
|
||
Main tab config in `src/app/(main)/(tabs)/_layout.tsx`:
|
||
|
||
```text
|
||
/home 首页
|
||
/space 空间, but route currently missing
|
||
/tools center app-list button
|
||
/chat 聊天
|
||
/my 我的
|
||
```
|
||
|
||
Finance module tab config in `src/app/finance/(tabs)/_layout.tsx`:
|
||
|
||
```text
|
||
/finance/home 记账
|
||
/finance/add center add button
|
||
/finance/analysis 分析
|
||
```
|
||
|
||
Layout behavior:
|
||
|
||
- Root `_layout.tsx` renders a global Stack with no headers.
|
||
- `(main)/(tabs)/_layout.tsx` uses `AppLayout`, which renders `Slot`, top `Navbar`, and bottom `Tabbar`.
|
||
- `finance/(tabs)/_layout.tsx` uses `ModuleLayout`, a module-level shell with its own top bar and tabbar.
|
||
- `ModuleLayout` close action returns to `/(main)/(tabs)/home`.
|
||
|
||
Current route mismatches:
|
||
|
||
- Main tab points to `/space`, but no `/space` file exists.
|
||
- `/tools` is inside `(main)` group, wrapped by root layout only.
|
||
- `/finance/add` is outside `finance/(tabs)`, but the finance tabbar points to it. This can be intentional for a modal/subpage style add screen.
|
||
|
||
## Feature Status
|
||
|
||
Implemented or mostly implemented:
|
||
|
||
- QQ login
|
||
- WeChat login
|
||
- Auth persistence
|
||
- Auth-expired redirect
|
||
- Family creation
|
||
- Family joining by invite code
|
||
- Family settings
|
||
- User profile
|
||
- Avatar upload to S3/RustFS
|
||
- QQ/WeChat account binding
|
||
- App/module list page
|
||
- Family activity feed (`/activities`)
|
||
|
||
Partially implemented:
|
||
|
||
- Finance module shell
|
||
- Finance category loading
|
||
- Finance add-record form UI (save not implemented)
|
||
- Finance record list on `/finance/home` (placeholder)
|
||
- Finance analysis placeholder/demo page
|
||
|
||
Not yet complete:
|
||
|
||
- Saving a finance record from `/finance/add`
|
||
- Finance record list on `/finance/home`
|
||
- Finance analysis real data
|
||
- Clothes matching app route (`/clothes/home` configured but route not implemented)
|
||
- `/space` main-tab page
|
||
|
||
## Database: allapp
|
||
|
||
Observed database:
|
||
|
||
- PostgreSQL version observed previously: 18.1
|
||
- Database: `allapp`
|
||
- Schema: `public`
|
||
- Explicit foreign keys: none observed
|
||
|
||
Tables:
|
||
|
||
```text
|
||
b_user
|
||
b_user_oauth
|
||
b_family
|
||
b_family_member
|
||
b_finance
|
||
b_finance_category
|
||
b_finance_category_default
|
||
b_app_favorite
|
||
```
|
||
|
||
Views:
|
||
|
||
```text
|
||
v_family_member
|
||
```
|
||
|
||
Main entities:
|
||
|
||
- `b_user`: user profile, nickname, avatar, last login time, name, phone, birth date.
|
||
- `b_user_oauth`: third-party login bindings; includes `user_id`, `type`, `openid`.
|
||
- `b_family`: family/space; includes `name`, `invite_code`, `owner_id`.
|
||
- `b_family_member`: family membership; includes `family_id`, `user_id`, `role`, `nickname`.
|
||
- `b_finance`: finance records; includes `category_id`, `family_id`, `amount`, `remark`, `record_time`.
|
||
- `b_finance_category_default`: default finance category template.
|
||
- `b_finance_category`: actual finance categories for a family.
|
||
- `v_family_member`: joins `b_family_member` with `b_user`.
|
||
|
||
Finance conventions:
|
||
|
||
```text
|
||
type = 0 expense
|
||
type = 1 income
|
||
```
|
||
|
||
Category icon names match frontend iconfont names, for example:
|
||
|
||
```text
|
||
finance_jiaotong
|
||
finance_canyin
|
||
finance_shicai
|
||
finance_gouwu
|
||
finance_gongzi
|
||
finance_jianzhi
|
||
finance_shouru
|
||
```
|
||
|
||
## Local Environment Notes
|
||
|
||
The Codex shell runs inside WSL2 Ubuntu.
|
||
|
||
WSL has `fnm` installed at:
|
||
|
||
```text
|
||
/home/oneao/.local/share/fnm/fnm
|
||
```
|
||
|
||
Known WSL Node setup:
|
||
|
||
```text
|
||
fnm 1.39.0
|
||
v22.22.3
|
||
v24.16.0 default
|
||
```
|
||
|
||
In non-interactive Codex commands, `~/.bashrc` may not initialize fnm automatically. If `node` is not
|
||
found, temporarily enable it with:
|
||
|
||
```bash
|
||
export PATH="$HOME/.local/share/fnm:$PATH"
|
||
eval "$(fnm env --shell bash)"
|
||
```
|
||
|
||
After fnm init, expected tools:
|
||
|
||
```text
|
||
node v24.16.0
|
||
pnpm 11.5.2
|
||
```
|
||
|
||
Windows-side `nvm` paths may appear in PATH:
|
||
|
||
```text
|
||
/mnt/d/devtool/nvm
|
||
/mnt/d/devtool/nodejs
|
||
```
|
||
|
||
Those are not the right runtime for WSL. Prefer WSL `fnm`.
|
||
|
||
## Known Type/Build Issues
|
||
|
||
Frontend:
|
||
|
||
- `pnpm exec tsc --noEmit` may have type errors.
|
||
- Generated iconfont components may have `react-native-svg` prop type incompatibilities.
|
||
|
||
Backend:
|
||
|
||
- Go 1.25 with Fiber v3.1 should compile without issues.
|
||
|
||
## Important Current Risks
|
||
|
||
- Generic backend data APIs need server-side permissions before production use.
|
||
- Finance category loading currently queries all `b_finance_category` rows without filtering by current `family_id`.
|
||
- Frontend business logic often trusts local/user-provided family and user IDs.
|
||
- Main tab route `/space` is missing.
|
||
- Some pages are placeholders or demos.
|
||
- Database credentials are exposed in `db.local.yml` (should not be committed to version control).
|
||
|
||
## Development Conventions
|
||
|
||
These rules MUST be followed. They exist to keep the codebase consistent and avoid unnecessary
|
||
duplication.
|
||
|
||
### API — No New Dedicated Endpoints
|
||
|
||
The project already has a set of generic data APIs. Do NOT create dedicated REST endpoints for
|
||
CRUD operations. Always use the existing generic APIs instead:
|
||
|
||
| Generic API | Usage |
|
||
|---|---|
|
||
| `POST /data/loadData` | Query with `view_name` (table/view), `search_condition`, `order_by`, `search_columns`, `args` |
|
||
| `POST /data/loadDataBySql` | Raw SQL query with `sql` and `args` |
|
||
| `POST /data/saveData` | Batch insert/update/delete by `table_name` and `key_field` |
|
||
| `POST /data/getUniqueId` | Get snowflake IDs, `count` param (1–100) |
|
||
|
||
If you genuinely believe a new endpoint is needed, **ask the user first**. Even then, new endpoints
|
||
should be generic (e.g. a new generic helper), not dedicated to a single feature.
|
||
|
||
### Database — Standard Audit Columns
|
||
|
||
All business tables MUST include these four standard columns:
|
||
|
||
```sql
|
||
create_time TIMESTAMPTZ -- auto-set by applyMetaFields on insert
|
||
update_time TIMESTAMPTZ -- auto-set by applyMetaFields on insert & update
|
||
create_by BIGINT -- auto-set by applyMetaFields on insert (from JWT user_id)
|
||
update_by BIGINT -- auto-set by applyMetaFields on update (from JWT user_id)
|
||
```
|
||
|
||
The `applyMetaFields` function in `pkg/db/meta_field.go` injects these automatically for all tables
|
||
except those in the `auditExcludeTables` list (`b_user`, `b_user_oauth`).
|
||
|
||
When creating a new table:
|
||
1. Include all four columns
|
||
2. Do NOT add the table to `auditExcludeTables` unless it is a system-level table (user/auth)
|
||
3. Primary key `id` should be a snowflake BIGINT, obtained via `getUniqueIdApi` on the frontend or
|
||
`uniqueid.NextId()` on the backend
|
||
|
||
### Frontend State — Reuse Existing Stores
|
||
|
||
Before creating a new Zustand store or API wrapper, check whether the data can be fetched via the
|
||
existing `loadDataApi` / `saveDataApi` from the `@/request/api` module. New API wrapper functions
|
||
should only wrap these generic endpoints — never introduce feature-specific RPC-style endpoints.
|
||
|