# Implementation Plan: User Auth + Server-Side Lives Management ## Current State Analysis ### Client (Cocos Creator) - **Currency**: "Lives" (生命值), new user starts with 10 - **Earning**: +1 life per correct answer (`PageLevel.showSuccess()` → `addLife()`) - **Spending**: -1 life per hint unlock (hints 2 & 3 only, `PageLevel.onUnlockClue()` → `consumeLife()`) - **Storage**: All local via `StorageManager` using `sys.localStorage` - **No auth**: WxSDK only handles sharing/vibration, no `wx.login` - **HttpUtil**: Has GET/POST, but no auth headers ### Server (NestJS) - Read-only: 4 GET endpoints (configs + levels) - No auth, no user system, no guards - Uses repository pattern consistently --- ## Phase 1: Server - Auth Module (WeChat Login) ### 1.1 Install dependencies ```bash cd MemeMind-Server pnpm add @nestjs/jwt axios ``` ### 1.2 New files to create **Entity: `src/modules/auth/entities/user.entity.ts`** - `id` (UUID, PK) - `openid` (varchar 128, unique index) - `sessionKey` (varchar 255, nullable) - WeChat session_key - `nickname` (varchar 100, nullable) - `avatarUrl` (text, nullable) - `lives` (int, default 10) - 生命值/积分 - `createdAt`, `updatedAt` **DTO: `src/modules/auth/dto/wx-login.dto.ts`** - `WxLoginRequestDto` - `{ code: string }` - `WxLoginResponseDto` - `{ token: string, user: { id, nickname, lives } }` **Repository: `src/modules/auth/repositories/user.repository.ts`** - `findByOpenid(openid)`, `findById(id)`, `create(data)`, `save(user)` **Service: `src/modules/auth/auth.service.ts`** - `wxLogin(code)`: Call WeChat API `https://api.weixin.qq.com/sns/jscode2session` with appid/secret + code → get openid/session_key → find or create user → sign JWT → return token + user info **Guard: `src/common/guards/jwt-auth.guard.ts`** - Custom Guard: extract Bearer token from header → verify JWT → attach user to request **Controller: `src/modules/auth/auth.controller.ts`** - `POST /v1/auth/wx-login` - public endpoint, accepts `{ code }`, returns `{ token, user }` **Module: `src/modules/auth/auth.module.ts`** - Imports: JwtModule, TypeOrmModule.forFeature([User]) - Exports: JwtModule (so other modules can use JwtService) ### 1.3 Environment variables Add to `.env` and `env.validation.ts`: - `WX_APPID` - 微信小程序 AppID - `WX_SECRET` - 微信小程序 AppSecret - `JWT_SECRET` - JWT signing secret --- ## Phase 2: Server - User Assets API (Lives Management) ### 2.1 New files **DTO: `src/modules/auth/dto/user-assets.dto.ts`** - `UserAssetsResponseDto` - `{ lives: number }` - `ConsumeLifeRequestDto` - `{ reason: 'hint_unlock', levelId?: string, hintIndex?: number }` - `EarnLifeRequestDto` - `{ reason: 'level_complete', levelId: string }` **Endpoints added to auth controller (or new user controller):** - `GET /v1/user/assets` - [Auth Required] Get current lives - `POST /v1/user/assets/consume` - [Auth Required] Consume 1 life (for hint unlock) - `POST /v1/user/assets/earn` - [Auth Required] Earn 1 life (for level completion) ### 2.2 Business logic safety - **Consume**: Check lives > 0 before deducting, return error if insufficient - **Earn**: Server validates the reason, +1 life - **Idempotency consideration**: For level_complete, track completed levels per user to prevent duplicate rewards ### 2.3 New Entity: `src/modules/auth/entities/user-level-progress.entity.ts` - `id` (UUID, PK) - `userId` (varchar, FK → User) - `levelId` (varchar, FK → Level) - `completedAt` (datetime) - Unique index on (userId, levelId) - prevent duplicate completion rewards --- ## Phase 3: Server - Protect Existing Endpoints + Loading Data API ### 3.1 Composite loading endpoint **`GET /v1/user/game-data`** - [Auth Required] Returns everything needed at loading: ```json { "user": { "id": "...", "lives": 10 }, "levels": [ ... ], // reuse existing level data "progress": { "completedLevelIds": ["level-1", "level-2"] } } ``` This replaces the client making multiple API calls during loading. ### 3.2 Auth on existing endpoints Keep `/v1/wechat-game/levels` and `/v1/wechat-game/configs` as **public** (no auth needed for level data). New user-specific endpoints require auth. --- ## Phase 4: Client - WeChat Login Integration ### 4.1 WxSDK - Add login method ```typescript static login(): Promise // returns wx code ``` Calls `wx.login()` → returns `code` ### 4.2 New file: `assets/scripts/utils/AuthManager.ts` Singleton managing auth state: - `login()`: WxSDK.login() → POST /v1/auth/wx-login → store token + user data - `getToken()`: return cached token - `getUserLives()`: return cached lives - `isLoggedIn()`: boolean - Store token in localStorage ### 4.3 HttpUtil - Add auth support - Add `setAuthToken(token)` static method - Modify GET/POST to attach `Authorization: Bearer ` header when token exists --- ## Phase 5: Client - Connect Lives to Server ### 5.1 New file: `assets/scripts/utils/UserAssetsManager.ts` Singleton managing user assets (lives) with server sync: - `fetchAssets()`: GET /v1/user/assets → update local lives - `consumeLife(reason, levelId?, hintIndex?)`: POST /v1/user/assets/consume → update local - `earnLife(reason, levelId)`: POST /v1/user/assets/earn → update local - Falls back to local StorageManager if network fails ### 5.2 PageLoading - Updated flow ``` start() → WxSDK.login() get code → POST /auth/wx-login → get token + user data (including lives) → Store token, sync lives to StorageManager → GET /levels (existing, now with auth optional) → Preload assets → Open PageHome ``` ### 5.3 PageLevel - Updated logic - `onUnlockClue()`: Call `UserAssetsManager.consumeLife('hint_unlock', levelId, hintIndex)` instead of `StorageManager.consumeLife()` - `showSuccess()` → `nextLevel()`: Call `UserAssetsManager.earnLife('level_complete', levelId)` instead of `StorageManager.addLife()` - Keep StorageManager as local cache/fallback --- ## File Change Summary ### Server - New Files (10 files) 1. `src/modules/auth/auth.module.ts` 2. `src/modules/auth/auth.controller.ts` 3. `src/modules/auth/auth.service.ts` 4. `src/modules/auth/entities/user.entity.ts` 5. `src/modules/auth/entities/user-level-progress.entity.ts` 6. `src/modules/auth/repositories/user.repository.ts` 7. `src/modules/auth/repositories/user-level-progress.repository.ts` 8. `src/modules/auth/dto/wx-login.dto.ts` 9. `src/modules/auth/dto/user-assets.dto.ts` 10. `src/common/guards/jwt-auth.guard.ts` ### Server - Modified Files (3 files) 1. `src/app.module.ts` - Import AuthModule 2. `src/config/env.validation.ts` - Add WX_APPID, WX_SECRET, JWT_SECRET 3. `src/main.ts` - Add Bearer auth to Swagger config ### Client - New Files (2 files) 1. `assets/scripts/utils/AuthManager.ts` 2. `assets/scripts/utils/UserAssetsManager.ts` ### Client - Modified Files (4 files) 1. `assets/scripts/utils/WxSDK.ts` - Add `login()` method 2. `assets/scripts/utils/HttpUtil.ts` - Add auth token support 3. `assets/PageLoading.ts` - Add login flow before loading 4. `assets/prefabs/PageLevel.ts` - Use UserAssetsManager for earn/consume --- ## API Endpoints Summary | Method | Endpoint | Auth | Description | |--------|----------|------|-------------| | POST | `/v1/auth/wx-login` | No | WeChat code → JWT token | | GET | `/v1/user/assets` | Yes | Get user lives | | POST | `/v1/user/assets/consume` | Yes | Consume 1 life (hint) | | POST | `/v1/user/assets/earn` | Yes | Earn 1 life (level complete) | | GET | `/v1/user/game-data` | Yes | Loading composite endpoint | | GET | `/v1/wechat-game/levels` | No | Existing, stays public | | GET | `/v1/wechat-game/configs` | No | Existing, stays public |