From a85b6a79a8ae0541e015e410d64e4e1aed010de1 Mon Sep 17 00:00:00 2001 From: pmc <740076875@qq.com> Date: Thu, 7 May 2026 10:58:29 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=98=A0=E5=B0=84=20qy-lty-admin=20?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../.planning/codebase/ARCHITECTURE.md | 203 ++++++ qy-lty-admin/.planning/codebase/CONCERNS.md | 655 ++++++++++++++++++ .../.planning/codebase/CONVENTIONS.md | 210 ++++++ .../.planning/codebase/INTEGRATIONS.md | 182 +++++ qy-lty-admin/.planning/codebase/STACK.md | 152 ++++ qy-lty-admin/.planning/codebase/STRUCTURE.md | 291 ++++++++ qy-lty-admin/.planning/codebase/TESTING.md | 279 ++++++++ 7 files changed, 1972 insertions(+) create mode 100644 qy-lty-admin/.planning/codebase/ARCHITECTURE.md create mode 100644 qy-lty-admin/.planning/codebase/CONCERNS.md create mode 100644 qy-lty-admin/.planning/codebase/CONVENTIONS.md create mode 100644 qy-lty-admin/.planning/codebase/INTEGRATIONS.md create mode 100644 qy-lty-admin/.planning/codebase/STACK.md create mode 100644 qy-lty-admin/.planning/codebase/STRUCTURE.md create mode 100644 qy-lty-admin/.planning/codebase/TESTING.md diff --git a/qy-lty-admin/.planning/codebase/ARCHITECTURE.md b/qy-lty-admin/.planning/codebase/ARCHITECTURE.md new file mode 100644 index 0000000..227a0a6 --- /dev/null +++ b/qy-lty-admin/.planning/codebase/ARCHITECTURE.md @@ -0,0 +1,203 @@ +# Architecture + +**Analysis Date:** 2026-05-07 + +## Pattern Overview + +**Overall:** Next.js 15 App Router with client-centric admin dashboard, role-based access control (RBAC) via localStorage permission matrix, and layered API abstraction. + +**Key Characteristics:** +- Client-side rendering-first pattern with permission checks in layout/shell components +- Token-based authentication with Axios interceptors for transparent token injection +- Role-based module access controlled by permission matrix in `lib/permissions.ts` +- Form handling via React Hook Form + Zod for validation +- Shadcn-style UI components copied into `components/ui/` (not npm packages) + +## Layers + +**Page Layer (Presentation/Routing):** +- Purpose: App Router page components that mount client-side shells and load data +- Location: `app/[module]/page.tsx` (e.g., `app/outfits/page.tsx`, `app/login/page.tsx`) +- Contains: "use client" page entries, form pages, list views +- Depends on: DashboardShell, custom feature components, API clients +- Used by: Next.js App Router, browser navigation + +**Component Layer (UI & Business Logic):** +- Purpose: Reusable business components (modals, dialogs, tables, feature-specific UI) +- Location: `components/` (feature folders) and `components/ui/` (primitives) +- Contains: DashboardShell, Sidebar, feature dialogs (AddOutfitDialog, DeleteConfirmationDialog), stat cards +- Depends on: UI primitives, API clients, React Hook Form, icons (lucide-react) +- Used by: Page components + +**API Client Layer (HTTP Communication):** +- Purpose: Axios-based API communication with interceptors for auth, error handling +- Location: `lib/api/` (e.g., `lib/api/client.ts`, `lib/api/auth.ts`, `lib/api/outfits.ts`) +- Contains: API singleton instance, interceptors, module-specific request/response adapters +- Depends on: Axios, localStorage (for token retrieval), environment variables +- Used by: Page components and feature components + +**Permission/Auth Layer (Access Control):** +- Purpose: Role-based module visibility and path-level permission checks +- Location: `lib/permissions.ts` (RBAC matrix), `middleware.ts` (route protection) +- Contains: PERMISSION_MATRIX (role → module[]), utility functions (hasPermission, getUserRole, getModuleFromPath) +- Depends on: localStorage.user_role, localStorage.is_superuser +- Used by: DashboardShell, Sidebar, middleware + +**Utility Layer:** +- Purpose: Type definitions, helpers, environment detection +- Location: `lib/utils.ts`, `lib/api/types.ts`, `lib/api/adapters.ts` +- Contains: Tailwind utilities (cn()), type mappings, mock data +- Depends on: Nothing (standalone) +- Used by: All layers + +## Data Flow + +**Authentication Flow (Login → Authenticated State):** + +1. User fills login form on `app/login/page.tsx` +2. Form submission calls `emailLogin(email, password)` from `lib/api/auth.ts` +3. Axios POSTs to `{NEXT_PUBLIC_API_BASE_URL}/v1/admin/login/` +4. Response contains `token`, `role`, `is_superuser` +5. `saveAuthToken(token, isSuperUser, role)` stores to localStorage and Cookie +6. Page redirects to `/` (dashboard) +7. Middleware checks Cookie `auth_token`; on subsequent page loads, Axios request interceptor injects token from localStorage + +**Protected Page Access Flow:** + +1. User navigates to protected route (e.g., `/outfits`) +2. Middleware checks cookie; if missing, redirects to `/login` +3. Page mounts, DashboardShell component renders +4. DashboardShell calls `hasPathPermission(pathname)` at mount +5. Function checks localStorage.user_role against PERMISSION_MATRIX +6. If denied, renders access-denied UI; if allowed, renders children +7. Sidebar calls `hasPermission(module)` for each menu item (mounted only) to filter visible items +8. Sidebar also displays current role from localStorage.user_role + +**Data Fetching Flow (Page → Component → API → Backend):** + +1. Page component (e.g., `app/outfits/page.tsx`) mounts as "use client" +2. Page renders feature components (e.g., OutfitsList, OutfitsTable) +3. Feature component calls `getOutfits(params)` from `lib/api/outfits.ts` +4. API function calls `apiClient.get('/card/category/clothing/?...')` +5. Request interceptor injects token: `Authorization: Bearer {token}` +6. Backend receives request with token in header +7. Response returns `{ success, code, data, message }` +8. API adapter function maps backend response to frontend type (e.g., `mapBackendOutfit`) +9. Component stores result in React state or updates UI +10. Response interceptor handles 401 (token expired) → clears localStorage → redirects to `/login` + +**State Management:** + +- **Token/Auth state:** Persisted in `localStorage.auth_token`, `localStorage.user_role`, `localStorage.is_superuser`; synced to Cookie `auth_token` +- **Component state:** React useState (shallow, page-scoped) +- **Form state:** React Hook Form (form-scoped) +- **No global state manager** (Redux/Zustand not used) + +**Authentication Token Lifecycle:** + +1. Stored in localStorage + Cookie (7-day expiry) after login +2. On every API request, request interceptor reads from localStorage +3. If 401 response, token deleted from localStorage, user redirected to `/login` +4. Cookie-based fallback allows middleware to check auth on route entry + +## Client/Server Component Split + +**Server Components:** +- `app/layout.tsx` — Root layout with metadata only; does NOT render protected content +- Most pages are **"use client"** due to permission checks and hooks + +**Client Components ("use client"):** +- All pages under `app/[module]/` — Need React hooks for permission checks, form handling +- `components/dashboard-shell.tsx` — Runs permission checks via `hasPathPermission()` +- `components/sidebar.tsx` — Uses `usePathname()`, `useRouter()`, `useState()` for role-based filtering +- Feature components — Use useState, API calls, form handling + +## Key Abstractions + +**DashboardShell:** +- Purpose: Layout wrapper that enforces route-level permissions +- Location: `components/dashboard-shell.tsx` +- Pattern: Client wrapper checking `hasPathPermission(pathname)` on mount; renders either access-denied UI or children +- Used by: All page components (wraps page content) + +**Sidebar:** +- Purpose: Dynamic navigation menu filtered by user role +- Location: `components/sidebar.tsx` +- Pattern: Client component reading `localStorage.user_role` after mount; filters menu items via `hasPermission(module)` +- Menu structure: Three sections (AI Admin, Content Admin, System Admin) with conditional rendering +- Icon mapping: MenuItem interface + lucide-react icons + +**API Client Singleton:** +- Purpose: Centralized HTTP client with reusable interceptors +- Location: `lib/api/client.ts` exports `apiClient` (Axios instance) +- Pattern: Single instance created at module load; request/response interceptors for auth/error handling +- Interceptors: Request injects token; response handles 401 by clearing token + redirecting + +**Permission Matrix:** +- Purpose: Role → Module[] mapping +- Location: `lib/permissions.ts` PERMISSION_MATRIX constant +- Pattern: Record with 5 roles × 13 modules +- Functions: getUserRole() → RoleName; hasPermission(module) → boolean; getModuleFromPath(pathname) → module + +**API Adapter Pattern:** +- Purpose: Map backend response schema to frontend type +- Example: `mapBackendOutfit()` in `lib/api/outfits.ts` +- Pattern: Backend returns `{ id, name, image_url, rarity_display, ... }`; adapter extracts and renames to `{ id, name, imageUrl, rarity, ... }` +- Why: Frontend type contracts differ from backend schema + +## Entry Points + +**Browser Entry:** +- Location: `app/layout.tsx` (Root layout) +- Triggers: Initial page load or navigation +- Responsibilities: Renders HTML shell; child routes rendered by App Router + +**Auth Entry:** +- Location: `app/login/page.tsx` +- Triggers: Unauthenticated users accessing protected routes (redirected by middleware) +- Responsibilities: Render login form, call `emailLogin()`, save auth token, redirect to dashboard + +**Dashboard Entry:** +- Location: `app/page.tsx` +- Triggers: Authenticated users navigating to `/` or post-login redirect +- Responsibilities: Check auth status, render stats/charts, provide links to submodules + +**Protected Route Entry (Example):** +- Location: `app/outfits/page.tsx` +- Triggers: User navigates to `/outfits` +- Responsibilities: Wrap content in DashboardShell (permission check), render OutfitsList component, fetch data + +## Error Handling + +**Strategy:** Try-catch at API level; Axios interceptor handles 401; UI displays error toast or inline messages. + +**Patterns:** + +- **API Request Errors:** Caught in try-catch, logged, thrown to caller (page/component) +- **401 Unauthorized:** Interceptor clears token, redirects to `/login` +- **Form Validation Errors:** React Hook Form + Zod pre-submission; validation errors displayed inline +- **Permission Denied:** DashboardShell renders access-denied UI (no error thrown) +- **Mock Data Fallback:** `lib/api/client.ts` exports mock users/roles; can be used for offline testing + +## Cross-Cutting Concerns + +**Logging:** +- Verbose Axios logging in request/response interceptors (console.log with emoji prefixes) +- Error logging in catch blocks + +**Validation:** +- React Hook Form + Zod for form fields +- Backend response type-checked against ApiResponse interface + +**Authentication:** +- localStorage.auth_token + Axios Bearer header injection +- Middleware checks Cookie auth_token for route protection +- 401 response triggers token cleanup + redirect + +**Internationalization:** +- Chinese UI text hardcoded in components +- No i18n library used + +--- + +*Architecture analysis: 2026-05-07* diff --git a/qy-lty-admin/.planning/codebase/CONCERNS.md b/qy-lty-admin/.planning/codebase/CONCERNS.md new file mode 100644 index 0000000..c13d365 --- /dev/null +++ b/qy-lty-admin/.planning/codebase/CONCERNS.md @@ -0,0 +1,655 @@ +# Codebase Concerns + +**Analysis Date:** 2026-05-07 + +## Tech Debt + +### Multiple Lock Files Conflict Risk + +**Issue:** Three conflicting lock files coexist: `package-lock.json`, `pnpm-lock.yaml`, and `yarn.lock`. + +**Files:** +- `package-lock.json` (177KB) +- `pnpm-lock.yaml` (2.3KB) +- `yarn.lock` (91KB) + +**Impact:** +- Risk of installing different dependency versions across environments +- CI/CD, Docker builds, and local development may pull incompatible versions +- Lock file merge conflicts during multi-developer collaboration +- Dockerfile uses yarn exclusively, but npm/pnpm may be used locally, creating version drift + +**Current State:** +- `Dockerfile` (line 12, 34) explicitly uses `yarn.lock*` +- `.gitignore` likely checks in all three files +- CLAUDE.md explicitly warns "不要混用" but provides no automation to enforce it + +**Fix Approach:** +1. Choose single package manager (recommend yarn, per Dockerfile) +2. Delete `package-lock.json` and `pnpm-lock.yaml` from git history +3. Add enforcement to `.gitignore` and CI/CD (fail if multiple lock files modified) +4. Document in CLAUDE.md that ONLY yarn should be used + +**Priority:** High - impacts build reproducibility and team workflow + +--- + +### Production Build Ignores TypeScript & ESLint Errors + +**Issue:** Build succeeds despite type/lint errors; production may contain undetected bugs. + +**Files:** +- `next.config.mjs:16-21` — Both `ignoreDuringBuilds: true` + +**Current Configuration:** +```javascript +eslint: { + ignoreDuringBuilds: true, +}, +typescript: { + ignoreBuildErrors: true, +}, +``` + +**Impact:** +- TypeScript errors (type mismatches, missing nullchecks) not caught at build time +- ESLint warnings (unused imports, unreachable code) ignored +- QA burden shifts to runtime discovery +- Breaking changes in dependencies not detected early + +**Evidence:** README.md (line 121) acknowledges this: "务必在本地执行 `next lint` 与 `tsc --noEmit` 保证质量" — manual step, not enforced + +**Fix Approach:** +1. Set both to `false` in production config +2. Fix all current type/lint errors (likely small count given ~27K LOC) +3. Add pre-commit hooks to `husky` to block commits with errors +4. Make lint/type checks mandatory in CI/CD + +**Priority:** High - gates correctness + +--- + +### Console Logging in Client Code (Debug Artifact) + +**Issue:** Production code contains console.log/warn/error calls, exposing debug info and token prefixes. + +**Files & Line Numbers:** +- `lib/api/client.ts:23` — `console.log('🔍 Token检查:', ...)` +- `lib/api/client.ts:29` — `console.warn('⚠️ 未找到auth_token...')` +- `lib/api/client.ts:35` — `console.log('📡 发送请求:', ...)` +- `lib/api/client.ts:36` — `console.log('📋 请求头:', ...)` +- `lib/api/client.ts:47` — `console.log('✅ 响应成功:', ...)` +- `lib/api/client.ts:51` — `console.error('❌ 响应错误:', ...)` +- `lib/api/client.ts:55` — `console.warn('🚫 认证失败...')` +- `lib/api/client.ts:56` — `console.log('响应详情:', ...)` +- `lib/api/upload.ts:33` — `console.log('📤 开始上传文件:', ...)` +- `lib/api/upload.ts:43` — `console.log(...上传进度...)` +- `lib/api/upload.ts:59` — `console.log('✅ 文件上传成功:', ...)` +- `lib/api/upload.ts:62` — `console.error("文件上传失败:", error)` +- Similar issues in upload multifile (lines 83, 93) +- `components/sidebar.tsx:97` — `console.error("退出登录失败:", error)` +- `app/outfits/page.tsx:72, 117, 135, 154` — console.error calls +- Many other pages: `app/*/page.tsx` contain error logging + +**Impact:** +- Token prefixes exposed in browser console → XSS/developer tools leak risk +- Error details (e.g., API paths, status codes) visible to end users +- Production debugging should use structured error handling, not console +- Could be scraped by malicious actors with browser console access + +**Evidence:** `lib/api/token-debug.ts` is entire debug utility with `console.log` throughout (lines 7-54), showing token prefix extraction logic + +**Fix Approach:** +1. Remove all console.log/warn/error in client code, replace with structured error handling +2. Create error boundary/toast notification pattern for user-facing errors +3. Optional: Create client-side error tracking service (Sentry) for production +4. Add ESLint rule: `no-console` (already available in Next.js config, just not enabled) + +**Priority:** High - security and code cleanliness + +--- + +## Security Concerns + +### Client-Side Permission Enforcement Only (Trust Boundary Violation) + +**Issue:** Permissions are enforced ONLY on client; backend may not re-validate on API calls. + +**Files:** +- `lib/permissions.ts:65-87` — `getUserRole()`, `hasPermission()` read from `localStorage.user_role` +- `components/sidebar.tsx:102-104` — Filter menu items client-side only +- `middleware.ts:17-44` — Token validation in middleware, but NO permission check against module being accessed + +**Risk Model:** +``` +User (attacker) → Modify localStorage.user_role="超级管理员" → + Sidebar shows all menus (useless) BUT + Direct API call to DELETE /api/v1/admin/users/123 → Backend accepts if no re-check +``` + +**Current Evidence:** +- `lib/api/users.ts:24-43` — `getUsers()` makes raw API call; no permission guard in client code +- No evidence of backend role check in Django API (not inspectable from frontend code, but CRITICAL to verify) + +**Impact:** +- **CRITICAL:** If backend doesn't re-validate role on each endpoint, attackers can: + - Modify localStorage role → make admin API calls + - Bypass middleware token check (SSR can't access localStorage) → direct API calls with stolen token + - Perform unauthorized operations (delete users, change settings, etc.) + +**Trust Boundary Rule:** Backend MUST re-check permission for EVERY admin API call, regardless of client claims. + +**Fix Approach:** +1. **URGENT:** Audit Django backend API (`qy_lty/lib/api/v1/admin/`) to confirm all endpoints validate user role/permissions +2. Document in CLAUDE.md: "Client-side permission filters are UI courtesy only; backend MUST validate on all admin/ endpoints" +3. Add explicit checks in frontend error handling for 403 Forbidden responses +4. Consider adding request logging/monitoring to catch unauthorized attempts + +**Priority:** CRITICAL - security vulnerability if backend doesn't re-check + +--- + +### Token Storage in localStorage (XSS Vulnerability) + +**Issue:** Auth token stored in `localStorage` instead of secure HttpOnly cookie; vulnerable to XSS. + +**Files:** +- `lib/api/auth.ts:48-59` — Stores token in localStorage AND sets js-cookie +- `lib/api/client.ts:21-22` — Reads token from localStorage in request interceptor +- `middleware.ts:32` — Token read from Cookie (correct), but localStorage is fallback + +**Current Implementation:** +```typescript +// Stored in BOTH localStorage and cookie +localStorage.setItem("auth_token", token); // XSS risk +Cookies.set("auth_token", token, { expires: 7, path: '/' }); // No HttpOnly flag +``` + +**Impact:** +- XSS attack can steal token from localStorage +- js-cookie (js-cookie, no HttpOnly flag) also vulnerable to XSS +- 7-day expiry without rotation = long XSS window +- `localStorage.user_role` also stored unencrypted (attackable) + +**Proper Secure Pattern:** +- HttpOnly, Secure, SameSite cookies (set by backend during login) +- Token never exposed to JavaScript +- Client reads from Bearer token response, never stores in accessible storage + +**Fix Approach:** +1. Backend should set HttpOnly cookie during login response +2. Remove localStorage token storage entirely +3. Keep only sealed HTTP-only cookie for auth +4. If SSR requires token access, use secure session storage (not localStorage) +5. Add CSP (Content-Security-Policy) headers to mitigate XSS + +**Priority:** High - significant security risk + +--- + +### No Token Rotation / Refresh Strategy + +**Issue:** Token expires in 7 days, no refresh token or rotation mechanism visible. + +**Files:** +- `lib/api/auth.ts:63` — `Cookies.set("auth_token", token, { expires: 7, path: '/' })` +- `lib/api/client.ts:54-60` — 401 handler just redirects to /login, doesn't attempt refresh + +**Impact:** +- User activity > 7 days → auto-logout (UX degradation) +- No short-lived access token + long-lived refresh token pattern +- Stolen token valid for full 7 days +- No token revocation (logout doesn't invalidate server-side if stateless JWT) + +**Fix Approach:** +1. Implement short-lived access token (15-30 min) + refresh token (7-14 days) +2. Refresh interceptor: if 401, attempt token refresh before redirecting to /login +3. Backend session should blacklist tokens on logout +4. Document token lifecycle in CLAUDE.md + +**Priority:** Medium - acceptable for internal admin tool, but should improve for production + +--- + +### No Input Validation on File Upload + +**Issue:** File uploads accept any mimetype/size without validation. + +**Files:** +- `lib/api/upload.ts:25-65` — `uploadFile()` accepts File object, no size/type checks in client +- `components/ui/file-upload.tsx` — Likely has upload UI, needs validation + +**Impact:** +- Malicious files (executables, oversized) may reach backend +- Backend must validate, but client-side check is first defense +- No maximum file size enforced client-side + +**Fix Approach:** +1. Add file size limits (e.g., max 50MB) +2. Whitelist MIME types (images: image/jpeg, image/png, image/webp only) +3. Show user error before upload attempt +4. Verify backend enforces same limits + +**Priority:** Medium - backend should catch it, but client should be first defense + +--- + +## Fragile Areas + +### Large Pages with Mixed Concerns (1000+ line files) + +**Issue:** Several pages handle data fetching, UI rendering, and business logic in single component. + +**Large Files:** +- `app/affinity/page.tsx` (1005 lines) — Affinity rules, levels, interactions all in one page +- `components/ui/sidebar.tsx` (763 lines) — Navigation logic, permission filtering, logout in one component +- `app/songs/[id]/page.tsx` (651 lines) — Song detail page, editing, deletion mixed +- `app/songs/page.tsx` (644 lines) — Song list, search, pagination, CRUD operations + +**Fragility Risks:** +- Hard to test individual business logic +- Changes to one feature (e.g., affinity rules) risk breaking another (levels) +- Difficult to reuse UI logic across pages +- High cognitive load for maintenance + +**Safe Modification Pattern:** +1. Extract data-fetching logic to `lib/api/` hooks or services +2. Create smaller sub-components (e.g., ``, ``) +3. Use custom hooks for state management per feature +4. Keep pages as composition only + +**Example Refactor:** +```typescript +// Current: 1005-line page +// After: +export default function AffinityPage() { + return ( + + + + + + + ) +} +``` + +**Priority:** Medium - refactor as part of feature changes, not urgent + +--- + +### No Error Boundaries (Next.js error.tsx Files Missing) + +**Issue:** No error boundary components for graceful error handling in App Router. + +**Files:** +- `app/` has NO `error.tsx` files +- Only some leaf routes have `loading.tsx` (e.g., `app/outfits/loading.tsx`) + +**Current Error Handling:** +- Pages catch errors in `try/catch`, show toast notifications +- Uncaught errors propagate, may show raw error UI +- No fallback UI for API failures + +**Impact:** +- User sees blank page or browser error on network failure +- No recovery mechanism (retry button, fallback content) +- Log data lost if error page crashes + +**Next.js Error Boundary Pattern:** +```typescript +// app/error.tsx (catches errors in all children) +'use client' +export default function Error({ error, reset }) { + return ( +
+

Something went wrong

+ +
+ ) +} +``` + +**Fix Approach:** +1. Add `app/error.tsx` for app-level errors +2. Add segment-level `error.tsx` for feature-specific handling (e.g., `app/outfits/error.tsx`) +3. Keep try/catch for API calls, render fallback UI +4. Add `app/not-found.tsx` for 404 routes + +**Priority:** Low - pages have basic error handling via toasts, but UX could improve + +--- + +### Hardcoded API Base URL Fallback + +**Issue:** API client falls back to hardcoded URL if env var not set. + +**Files:** +- `lib/api/client.ts:9` — `API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000/api"` + +**Risk:** +- If `NEXT_PUBLIC_API_BASE_URL` not set in production, silently uses localhost +- Requests fail silently in production, confusing to debug +- No warning log when fallback is used + +**Fix Approach:** +1. Remove fallback; throw error if env var not set at build time +2. Use `if (!process.env.NEXT_PUBLIC_API_BASE_URL) throw new Error("...")` +3. Document required env vars in deployment guide + +**Priority:** Low - environmental issue, unlikely in production if properly configured + +--- + +## Performance Bottlenecks + +### No Memoization on Permission Checks + +**Issue:** `hasPermission()`, `getUserRole()` called on every render without memoization. + +**Files:** +- `components/sidebar.tsx:102-104` — Calls `hasPermission(item.module)` in `.filter()` on every render +- `lib/permissions.ts:65-87` — No caching; reads localStorage every time + +**Current Pattern:** +```typescript +const visibleAiItems = mounted ? aiMenuItems.filter((item) => hasPermission(item.module)) : [] +``` + +**Impact:** +- Sidebar re-renders, re-filters items, re-reads localStorage unnecessarily +- Minimal impact on small arrays (3-15 items), but scales poorly + +**Fix Approach:** +1. Memoize `getUserRole()` result in sidebar: +```typescript +const userRole = useMemo(() => getUserRole(), [mounted]) +const visibleItems = useMemo(() => + aiMenuItems.filter(item => hasPermission(item.module)), + [userRole] +) +``` +2. Or move to custom hook: `const { role, allowedModules } = usePermissions()` + +**Priority:** Low - not a visible performance issue, but good practice + +--- + +## Cross-Repo Coupling Risks + +### Strong Runtime Dependency on qy_lty Backend + +**Issue:** Front-end depends on specific API contract from `qy_lty/` Django backend; contract drift risk. + +**Files:** +- `lib/api/users.ts:6-21` — Maps backend `ParadiseUser` to `User` type (field mapping) +- `lib/api/outfits.ts` (not read, but expected) — Maps backend outfit schema +- ALL API modules expect specific response structure from `/api/v1/admin/*` + +**Coupling Points:** +- Backend field names: `username`, `email`, `phone_number`, `date_joined`, `last_login`, `is_superuser`, `is_staff` +- Response structure: `{ results: [...], count: number }` (Django REST Framework default) +- Endpoint paths: `/v1/admin/login/`, `/user/`, `/common/upload/` +- Token storage key in Redis: `admin_token:{token}` (per CLAUDE.md) + +**Risk Scenario:** +1. Backend dev changes `/v1/admin/users/` response format +2. Frontend still sends old field names or expects old response shape +3. Silent failures or type mismatches +4. Detected late in testing or production + +**Mitigation in Place:** +- CLAUDE.md (line 34) requires both repos update `docs/修改记录.md` when contract changes +- But no automated contract testing (OpenAPI, TypeScript codegen) + +**Fix Approach:** +1. **Shared types/API contract** (OpenAPI/GraphQL schema) versioned in git +2. Generate TypeScript types from backend OpenAPI spec (e.g., with Swagger-Codegen) +3. Add pre-commit hook to detect schema changes +4. Document in CLAUDE.md: "Any API response structure change requires updating both repos' docs" + +**Priority:** Medium - mitigated by manual process, but automatable + +--- + +## Dependencies at Risk + +### No Enforcement of Dependency Updates + +**Issue:** Many dependencies pinned to `latest`, creating unpredictable updates. + +**Files:** +- `package.json:12, 56, 63` — `"latest"` version specifiers + +**Current Versions Using "latest":** +```json +"@hookform/resolvers": "latest", +"react-hook-form": "latest", +"recharts": "latest", +"zod": "latest" +``` + +**Impact:** +- `npm install` at different times installs different major versions +- Breaking changes in `react-hook-form` or `recharts` not caught by lock file +- Tests pass locally, fail in CI if new major version published + +**Fix Approach:** +1. Replace all `"latest"` with specific versions (e.g., `"^7.0.0"` for caret range) +2. Use Dependabot or renovate to automate safe updates +3. Pin devDependencies to exact versions + +**Example:** +```json +"@hookform/resolvers": "^3.3.4", +"react-hook-form": "^7.51.0", +"recharts": "^2.12.0", +"zod": "^3.22.0" +``` + +**Priority:** Medium - not urgent, but good practice + +--- + +### Unused Dependencies or Overlapping Packages + +**Issue:** Unclear if all installed packages are actively used. + +**Potential Issues:** +- Multiple notification libraries: `sonner` + `@radix-ui/react-toast` (which is used?) +- Figure out which is preferred and remove duplication + +**Files:** +- `package.json:36, 59` — Both toast packages listed + +**Fix Approach:** +1. Audit each dependency in `package.json` +2. Remove unused packages +3. Consolidate duplicate functionality + +**Priority:** Low - cleanup task + +--- + +## Test Coverage Gaps + +### No Tests Found + +**Issue:** No Jest/Vitest configuration or test files detected in codebase. + +**Expected Test Files:** +- `*.test.ts`, `*.spec.ts`, `*.test.tsx`, `*.spec.tsx` not found +- No `jest.config.*`, `vitest.config.*` + +**Impact:** +- Complex logic (permission matrix, API adapters) not tested +- Refactoring risky (no regression protection) +- Onboarding new developers without safety net +- No confidence in critical paths (auth, user management) + +**Critical Areas Needing Tests:** +- `lib/permissions.ts` — Permission matrix logic (many branches) +- `lib/api/client.ts` — Request/response interceptors (error handling paths) +- `lib/api/users.ts` — User mapping logic (backend schema mismatches) +- Custom hooks in pages (data fetching, state management) + +**Fix Approach:** +1. Set up Jest or Vitest (Vitest recommended for ESM + modern tooling) +2. Create basic test for `lib/permissions.ts`: +```typescript +// lib/permissions.test.ts +describe('Permission checks', () => { + it('should allow super admin all modules', () => { + // Mock localStorage with 超级管理员 + expect(hasPermission('users')).toBe(true) + expect(hasPermission('ai-model')).toBe(true) + }) + + it('should restrict content admin to content modules', () => { + // Mock localStorage with 内容管理员 + expect(hasPermission('users')).toBe(false) + expect(hasPermission('outfits')).toBe(true) + }) +}) +``` +3. Aim for 80%+ coverage of critical API functions + +**Priority:** Medium - important for long-term maintainability + +--- + +## Missing Critical Features + +### No Logout Token Revocation + +**Issue:** `logout()` clears client-side storage but doesn't invalidate server-side token. + +**Files:** +- `lib/api/auth.ts:108-131` — `logout()` calls API endpoint, then clears localStorage + +**Problem:** +- If using stateless JWT, backend has no record of logout +- Token remains valid until expiry (7 days) +- Attacker with stolen token can still use it after target user "logs out" + +**Expected Behavior:** +- Server should blacklist token on logout (Redis blacklist or DB flag) +- Verify endpoint `/v1/admin/logout/` actually invalidates token + +**Fix Approach:** +1. Audit backend logout endpoint to confirm token is invalidated +2. Add token blacklist to backend (Redis: token → blacklisted, TTL=7 days) +3. Check token against blacklist on every request +4. Document in CLAUDE.md + +**Priority:** High - security issue if not handled on backend + +--- + +### No Rate Limiting on Client (Brute Force Risk) + +**Issue:** Login page may be vulnerable to brute force attacks. + +**Files:** +- `app/login/page.tsx` — Likely has login form +- `lib/api/auth.ts:25-40` — No rate limiting before API call + +**Impact:** +- Attacker can spam login attempts to guess password +- Backend must implement rate limiting, but client can't spam if it has throttling + +**Fix Approach:** +1. Client-side: Disable login button for 30 seconds after failed attempt +2. Backend: Implement account lockout (3 failed attempts = 10 min lockout) +3. Monitor login failures in logs + +**Priority:** Medium - backend responsibility, but client should assist + +--- + +## Build Configuration Issues + +### Next.js Standalone Output May Have Deployment Pitfalls + +**Issue:** Standalone mode used, but edge case behaviors not fully documented. + +**Files:** +- `next.config.mjs:30` — `output: 'standalone'` + +**What Standalone Does:** +- Bundles all dependencies into `.next/standalone` folder +- Reduces Docker image size (~100MB vs ~500MB) +- Requires explicit `COPY .next .next` in Dockerfile (correctly done) + +**Potential Issues:** +- Public assets not automatically copied; must COPY separately (done in Dockerfile:44-45) +- `next.config.mjs` must be manually copied (done in Dockerfile:46) +- Environment variables must be passed at runtime, not build time +- Debugging bundle issues harder (bundled deps not in node_modules) + +**Current Dockerfile (lines 44-46) handles this correctly:** +```dockerfile +COPY --from=builder /app/.next ./.next +COPY --from=builder /app/public ./public +COPY --from=builder /app/next.config.mjs ./ +``` + +**Risk:** +- If dependencies add new output files in future versions, they won't be copied +- Node version mismatch between builder (22.10.0) and runner (22.10.0) is correct but fragile + +**Fix Approach:** +1. Document standalone output caveats in deployment guide +2. Test Docker image monthly to ensure no drift in builder/runner +3. Consider base image pinning: `FROM node:22.10.0-alpine@sha256:...` + +**Priority:** Low - current setup is correct, just needs documentation + +--- + +### No Pre-commit Hooks or Husky Setup + +**Issue:** No automated checks before commits; developers can commit broken code. + +**Files:** +- No `.husky/` directory +- No `pre-commit` config +- CLAUDE.md doesn't mention hook setup + +**Risk:** +- Developer commits console.log, broken types, or lint errors +- CI catches them later, but feedback loop is slow +- No enforcement of modification record updates (CLAUDE.md requirement) + +**Fix Approach:** +1. Install husky: `npm install husky --save-dev` +2. Setup hook: `npx husky install` +3. Create `.husky/pre-commit`: +```bash +#!/bin/sh +npm run lint && npm run type-check +``` +4. Create `.husky/prepare-commit-msg` to remind user about docs/修改记录.md + +**Priority:** Low - improvement for team workflow + +--- + +## Summary Table + +| Category | Issue | Severity | Fix Effort | +|----------|-------|----------|-----------| +| Security | Client-only permissions (backend trust) | CRITICAL | High | +| Security | Token in localStorage (XSS) | High | Medium | +| Security | No token revocation on logout | High | High | +| Tech Debt | Lock file conflicts | High | Low | +| Tech Debt | Build ignores TS/ESLint errors | High | Low | +| Tech Debt | Console logging in production | High | Low | +| Fragility | Large 1000+ line pages | Medium | Medium | +| Performance | No memoization on permissions | Low | Low | +| Testing | Zero test coverage | Medium | High | +| Build | Standalone output not fully documented | Low | Low | + +--- + +*Concerns audit: 2026-05-07* diff --git a/qy-lty-admin/.planning/codebase/CONVENTIONS.md b/qy-lty-admin/.planning/codebase/CONVENTIONS.md new file mode 100644 index 0000000..d7861ca --- /dev/null +++ b/qy-lty-admin/.planning/codebase/CONVENTIONS.md @@ -0,0 +1,210 @@ +# 代码规范 + +**分析日期:** 2026-05-07 + +## 命名规范 + +**文件命名:** +- 组件文件:PascalCase + `.tsx` 扩展名(如 `DashboardShell.tsx`、`AddOutfitDialog.tsx`) +- 工具函数/库文件:kebab-case + `.ts` 扩展名(如 `error-handler.ts`、`client.ts`) +- 特殊情况:对话框类组件使用 kebab-case(如 `add-outfit-dialog.tsx`、`delete-confirmation-dialog.tsx`) + +**函数命名:** +- 使用 camelCase(如 `handleLogin`、`fetchOutfits`、`mapBackendOutfit`) +- API 函数:动词 + 名词,camelCase(如 `getOutfits`、`createOutfit`、`updateOutfit`、`deleteOutfit`) +- 事件处理函数:`handle` + 事件名(如 `handleSubmit`、`handleChange`、`handleSendVerificationCode`) +- 工具函数:动词 + 目标(如 `formatDate`、`toDisplayOutfit`) + +**变量命名:** +- 状态变量:camelCase(如 `email`、`password`、`isLoading`、`selectedOutfit`) +- 布尔值:`is` 或 `has` 前缀(如 `isLoading`、`isSubmitting`、`hasPermission`) +- 常量:UPPER_SNAKE_CASE(如 `TOAST_LIMIT`、`TOAST_REMOVE_DELAY`、`API_BASE_URL`) + +**类型/接口命名:** +- PascalCase(如 `EmailLoginResponse`、`DashboardShellProps`、`ApiResponse`) +- Props 接口:`组件名Props` 后缀(如 `ButtonProps`、`DashboardShellProps`) +- 联合类型:`RoleName`、`PermissionModule` + +## 代码样式 + +**格式化:** +- 使用 TypeScript strict 模式(`tsconfig.json` 中 `"strict": true`) +- 目标:ES6(`target: "ES6"`) +- 模块化解析:`bundler` 模式(用于 Next.js) +- 无 ESLint/Prettier 配置文件(`next.config.mjs` 中 `eslint.ignoreDuringBuilds: true`) +- 代码风格通过 TypeScript 编译器和 Next.js 内置检查 + +**缩进和空格:** +- 使用 2 空格缩进(根据代码库一致性) +- 函数声明和块语句间使用一致空格 + +**类型注解:** +- 所有函数参数必须有类型注解 +- 所有变量应有类型注解(特别是导出的接口和公共函数返回值) +- 使用 TypeScript 严格模式防止隐式 `any` + +## 导入组织 + +**顺序:** +1. React 和 Next.js 核心库(`import React from "react"`、`import { useState } from "react"`) +2. Next.js 功能(`import { useRouter } from "next/navigation"`、`import Link from "next/link"`) +3. 第三方库(`import axios from "axios"`、`import Cookies from "js-cookie"`) +4. 本项目组件(`import { Button } from "@/components/ui/button"`) +5. 本项目库和工具(`import { cn } from "@/lib/utils"`) +6. 本项目 hooks(`import { useToast } from "@/components/ui/use-toast"`) +7. 类型导入(`import type { ApiResponse } from "./client"`) + +**路径别名:** +- 项目配置 `@/*` 指向项目根目录 +- 组件:`@/components/...` +- 库函数:`@/lib/...` +- Hooks:`@/hooks/...` +- 类型和接口:通过 `@/lib/api/types` 集中导入 +- UI 组件:`@/components/ui/...` + +## 错误处理 + +**模式:** +- 使用 `try-catch` 处理异步操作和 API 调用(见 `auth.ts`、`outfits.ts`) +- 创建自定义错误类 `ApiError` 扩展 `Error`,包含 `status` 属性(见 `error-handler.ts` 第 46-54 行) +- 统一的错误处理函数 `handleApiException()` 将不同类型的错误转换为用户友好的消息(见 `error-handler.ts` 第 79-90 行) +- 错误信息映射表 `errorMessages` 定义特定错误代码(`USER_NOT_FOUND`、`ROLE_EXISTS` 等)的消息 + +**API 错误处理:** +- 在 Axios 响应拦截器中处理 401 未授权错误:清除 token 并重定向到登录页面(见 `client.ts` 第 54-61 行) +- 对 API 失败使用 Radix Toast 组件显示错误提示(见 `error-handler.ts` 第 69-76 行、第 93-100 行) +- 统一的 `handleApiRequest()` 包装器函数处理成功/失败情况并自动显示 toast(见 `error-handler.ts` 第 102-144 行) + +**日志:** +- 使用 `console.log()`、`console.warn()`、`console.error()` 进行调试和错误追踪 +- API 请求/响应在拦截器中记录详细日志,包括 token 检查、请求头、响应状态(见 `client.ts` 第 20-66 行) +- 使用 emoji 表情增强日志可读性(`🔍 Token检查`、`✅ Token已添加`、`❌ 响应错误`) + +## 日志记录 + +**框架:** `console`(浏览器原生) + +**使用规范:** +- API 请求/响应日志在 Axios 拦截器中集中管理 +- 所有认证流程加日志(token 检查、保存、清除) +- 错误信息带上上下文(URL、状态码、方法) +- 生产环境应考虑减少日志或使用第三方服务(未配置) + +## 注释 + +**何时写注释:** +- JSDoc 风格注释用于公共 API 函数(见 `auth.ts` 第 5-7 行、第 24-30 行) +- 注释解释"为什么"而不是"是什么" +- 复杂业务逻辑或算法需要行内注释 + +**JSDoc/TSDoc 风格:** +```typescript +/** + * 邮箱登录接口 + * @param email 邮箱 + * @param password 密码 + * @returns 包含token的响应 + */ +export const emailLogin = async (email: string, password: string): Promise => { + // ... +} +``` + +**中文注释:** 所有注释使用中文(符合 CLAUDE.md 的语言偏好) + +## 函数设计 + +**大小:** +- 函数长度通常 20-50 行(见 `handleLogin` 在 `login/page.tsx` 第 28-63 行) +- 超过 100 行的函数应考虑拆分(如 `AddOutfitDialog` 组件中的多步表单分离为 Tabs) + +**参数:** +- 单个参数优于多个参数 +- 对象参数用于选项(如 `handleApiRequest()` 的 options 对象,第 104-110 行) +- 类型参数用于泛型函数(如 `handleResponse()`、`ApiResponse`) + +**返回值:** +- 异步函数返回 `Promise` +- 布尔查询函数返回 `boolean`(如 `hasPermission()`、`isAuthenticated()`) +- 数据获取返回具体类型或 `null`(见 `getOutfit()` 返回 `Promise`) + +## 模块设计 + +**导出:** +- 公共 API 函数从 `lib/api/` 模块导出(如 `emailLogin`、`getOutfits`) +- 类型从 `lib/api/types.ts` 集中导出 +- Utility 函数从 `lib/utils.ts` 导出(如 `cn()` 用于样式合并) + +**Barrel 文件:** +- `lib/api/index.ts` 作为 barrel 文件,导出所有 API 函数以便统一导入 +- 组件库采用 Radix UI + shadcn 风格,每个组件文件独立,无 barrel 文件 + +## React 和组件规范 + +**状态管理:** +- 使用 React Hooks(`useState`、`useEffect`、`useCallback`)进行本地状态管理 +- 在客户端组件中集中管理表单状态(见 `add-outfit-dialog.tsx` 第 24-36 行) + +**组件模式:** +- 所有表单和交互组件用 `"use client"` 指令标记 +- Page 组件(`app/*/page.tsx`)使用 `"use client"` 支持交互 +- 使用 Radix UI 和 shadcn 风格组件库,组件文件在 `components/ui/` +- Props 接口扩展原生 HTML 属性(如 `ButtonProps extends React.ButtonHTMLAttributes`) + +**样式:** +- 使用 Tailwind CSS 类名 +- 使用 `cn()` 工具函数(基于 `clsx` + `tailwind-merge`)合并条件样式(见 `button.tsx` 第 47 行) +- 使用 `class-variance-authority` (CVA) 定义组件变体(见 `button.tsx` 第 7-34 行) +- 颜色、间距、圆角通过 Tailwind 配置变量(CSS 变量)定义 + +## 表单与验证 + +**表单状态:** +- 使用 `useState` 管理表单字段状态 +- 表单提交通过 `handleSubmit()` 函数处理(见 `add-outfit-dialog.tsx` 第 38-56 行) + +**验证:** +- 基本的客户端验证(如检查空值:`if (!formData.name || !formData.description)`) +- React Hook Form + Zod 在 package.json 中定义但代码中未广泛使用 +- 服务器端验证通过 API 响应处理(返回 400/422 等错误状态) + +**通知:** +- 成功/错误提示使用 Sonner toast 或 Radix Toast(通过 `useToast()` hook) +- toast 样式变体:`"default"`、`"destructive"`(见 `error-handler.ts` 第 71-75 行) + +## 权限控制 + +**权限检查:** +- 运行时权限检查通过 `hasPermission(module)` 函数(见 `permissions.ts` 第 85-87 行) +- 权限矩阵定义在 `PERMISSION_MATRIX` 对象,映射角色到模块列表 +- 路由保护:中间件 `middleware.ts` 检查 cookie 中的 token,无 token 重定向到登录 +- 组件级权限检查:`DashboardShell` 使用 `hasPathPermission()` 处理访问拒绝(见 `dashboard-shell.tsx` 第 23-24 行) + +**角色存储:** +- 登录后在 `localStorage` 中存储 `user_role` 字符串(见 `auth.ts` 第 58 行) +- 超级管理员标识存储为 `is_superuser`(见 `auth.ts` 第 53 行) +- token 同时存储在 `localStorage` 和 Cookie 中(见 `auth.ts` 第 49、63 行) + +## 异步操作 + +**模式:** +- 使用 `async/await` 处理异步 API 调用 +- 加载状态通过 `isLoading` 布尔值跟踪(见 `login/page.tsx` 第 19、30 行) +- 模拟延迟用于开发和测试(见 `client.ts` `simulateDelay()` 和 `mockResponse()` 函数) + +## API 集成 + +**Axios 配置:** +- 在 `lib/api/client.ts` 创建 Axios 实例,基础 URL 来自环境变量 `NEXT_PUBLIC_API_BASE_URL` +- 请求拦截器自动注入 Authorization 头(Bearer token) +- 响应拦截器处理 401 错误并触发重新登录 +- API 端点前缀:`/api/v1/admin/` + +**适配器模式:** +- 后端返回数据结构与前端显示结构不同 +- 使用 `mapBackendOutfit()` 等适配器函数转换数据(见 `outfits.ts` 第 5-22 行) +- 适配器在 `lib/api/adapters.ts` 中集中管理 + +--- + +*规范分析日期:2026-05-07* diff --git a/qy-lty-admin/.planning/codebase/INTEGRATIONS.md b/qy-lty-admin/.planning/codebase/INTEGRATIONS.md new file mode 100644 index 0000000..fdd98fc --- /dev/null +++ b/qy-lty-admin/.planning/codebase/INTEGRATIONS.md @@ -0,0 +1,182 @@ +# External Integrations + +**Analysis Date:** 2026-05-07 + +## APIs & External Services + +**qy_lty Backend (Django REST API):** +- Primary backend for all business logic +- URL: Base configured via `NEXT_PUBLIC_API_BASE_URL` (default: `http://localhost:8000/api`) +- SDK/Client: Axios (custom instance with interceptors) +- Auth: Bearer token via `Authorization: Bearer {token}` header +- Primary modules consumed: + - `/api/v1/admin/login/` - User authentication + - `/api/v1/admin/logout/` - User logout + - `/ai/bots/` - AI model CRUD + - `/card/category/clothing/` - Outfit/clothing items + - `/card/category/props/` - Props/accessories + - `/card/category/home-decor/` - Home decoration items + - `/card/category/food/` - Food items + - `/music/songs/` - Song management + - `/dances/` - Dance content + - `/achievements/` - Achievement system + - `/affinity/` - Affinity/favorability system + - `/common/upload/` - File upload endpoint + - `/common/upload/info/` - File metadata retrieval + +**Request/Response Pattern:** +```typescript +// Axios configuration: lib/api/client.ts +const apiClient = axios.create({ + baseURL: API_BASE_URL, + headers: { 'Content-Type': 'application/json' } +}) + +// Request interceptor: auto-injects token +apiClient.interceptors.request.use((config) => { + const token = localStorage.getItem('auth_token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config +}) + +// Response interceptor: handles 401 (redirect to /login) +apiClient.interceptors.response.use( + (response) => response, + (error) => { + if (error.response?.status === 401) { + localStorage.removeItem('auth_token') + window.location.href = '/login' + } + return Promise.reject(error) + } +) +``` + +## Data Storage + +**Databases:** +- No direct database connection from frontend +- Backend (qy_lty) manages all persistent data (PostgreSQL/MySQL presumed) +- Frontend uses in-memory mock data for fallback/demo scenarios only + +**File Storage:** +- Backend-managed via `/common/upload/` endpoint +- Upload types supported: + - **Images** (JPEG, PNG, GIF, WebP) - max 10MB + - **Avatars** (JPEG, PNG) - max 2MB + - **Audio** (MP3, WAV, OGG, AAC, FLAC, WMA, M4A) - max 20MB + - **Animations** (MP4, AVI, MOV, WMV, FLV, GIF, Lottie JSON) - max 50MB + - **General files** - multipart/form-data +- Upload library: File API (FormData) via Axios +- Progress tracking: `onUploadProgress` callback support + +**Caching:** +- Browser localStorage for: + - `auth_token` - Authentication token (auto-removed on 401) + - `is_superuser` - Superuser flag + - `user_role` - User role string (used for permission checks) + - `isLoggedIn` - Session state flag +- No server-side caching configured (Redis presumed in backend qy_lty) + +## Authentication & Identity + +**Auth Provider:** +- Custom implementation via qy_lty backend +- Backend OAuth/token system: admin token key format `admin_token:{token}` (in Redis) + +**Login Flow:** +1. POST `/api/v1/admin/login/` with email + password +2. Backend returns: `{ success, code, data: { token, is_superuser?, role? }, message }` +3. Frontend stores token in localStorage + cookies (7-day expiry) +4. All subsequent requests include `Authorization: Bearer {token}` +5. On 401 response: clear tokens, redirect to `/login` + +**Token Storage:** +- Primary: `localStorage.auth_token` (checked on every request) +- Secondary: `js-cookie` cookie `auth_token` (7-day expiry) for middleware access +- Logout clears both storages + +**Role-Based Access:** +- Roles stored in localStorage: `user_role` +- Permission matrix defined in `lib/permissions.ts` +- Supported roles: 超级管理员, 内容管理员, AI模型管理员, 卡牌管理员, 查看者, 管理员 +- Module-level access control via `hasPermission()` and `hasPathPermission()` functions + +**Protected Routes:** +- Middleware: `middleware.ts` checks for token on protected paths +- Protected paths: `/`, `/dashboard`, `/users`, `/roles`, `/ai-models`, `/outfits`, `/props`, `/songs`, `/settings` +- Public paths: `/login`, `/register`, `/forgot-password` (no token required) + +## Monitoring & Observability + +**Error Tracking:** +- Not detected - errors logged to console only +- Error messages mapped in `lib/api/error-handler.ts` +- Toast notifications via Sonner for user-facing errors + +**Logs:** +- Console logging (development-focused) +- Request/response logging in Axios interceptors (logs token status, URLs, headers, status codes) +- Client-side logging only (no centralized log aggregation) + +## CI/CD & Deployment + +**Hosting:** +- Docker containerization: `Dockerfile` (multi-stage build) +- Runtime: Node.js 22.10.0 Alpine Linux +- Port: 3000 +- Command: `yarn start` (runs Next.js production server) + +**CI Pipeline:** +- Not detected in codebase (likely external to this repo) + +**Build Output:** +- Format: Next.js standalone (self-contained, no `node_modules` in runtime image) +- Files included: `.next/standalone/`, `public/` +- Size optimization: devDependencies not included in runner stage + +## Environment Configuration + +**Required env vars:** +- `NEXT_PUBLIC_API_BASE_URL` - Backend API base URL (must be public, prefixed with `NEXT_PUBLIC_`) + - Example: `http://localhost:8000/api` (development), `https://api.production.com/api` (production) + +**Optional env vars:** +- `NODE_ENV` - Set to `production` in Docker runner stage +- `.env.local` - Overrides all other env files (gitignored) +- `.env.development` - Dev-specific overrides +- `.env.production` - Production-specific overrides + +**Secrets location:** +- Authentication tokens: browser localStorage + cookies +- No API keys or credentials hardcoded in source +- Environment variable `NEXT_PUBLIC_API_BASE_URL` is the sole configuration bridge to backend + +## Webhooks & Callbacks + +**Incoming:** +- None detected +- Backend (qy_lty) may have webhooks, but frontend is purely client-side consumer + +**Outgoing:** +- None detected +- All communication is request-response (REST API calls to qy_lty) + +## Cross-Repo Dependencies + +**qy_lty Backend (Sibling Repo):** +- Location: `C:\Users\admin\Desktop\Lila-Server\qy_lty\` (Django) +- Contract: `/api/v1/admin/` endpoint suite +- Shared concerns: Token format (`admin_token:{token}`), role names, permission structure +- Change coordination required: Both `docs/修改记录.md` files must be updated when API contracts change + +**Notes:** +- Frontend is tightly coupled to backend API schema (no API versioning detected) +- Backend controls: authentication, authorization, data persistence, file storage +- Frontend is purely a UI/UX layer consuming backend HTTP APIs + +--- + +*Integration audit: 2026-05-07* diff --git a/qy-lty-admin/.planning/codebase/STACK.md b/qy-lty-admin/.planning/codebase/STACK.md new file mode 100644 index 0000000..6b33376 --- /dev/null +++ b/qy-lty-admin/.planning/codebase/STACK.md @@ -0,0 +1,152 @@ +# Technology Stack + +**Analysis Date:** 2026-05-07 + +## Languages + +**Primary:** +- TypeScript 5 - All application code, types, and configuration +- React 19 - UI components and application logic (via Next.js) +- CSS / Tailwind - Styling + +**Secondary:** +- JavaScript - Configuration files, optional runtime + +## Runtime + +**Environment:** +- Node.js 22.10.0 (Alpine Linux) - as specified in Dockerfile + +**Package Manager:** +- Yarn (primary for Docker builds; npm/pnpm also supported locally) +- Lockfiles: `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock` present +- Registry: npm registry (Dockerfile uses Taobao mirror `registry.npmmirror.com` for builds) + +## Frameworks + +**Core:** +- Next.js 15.2.4 - App Router, React Server Components, standalone output mode + - Configuration: `next.config.mjs` + - Build output: standalone (optimized for Docker) + - Experimental features: webpackBuildWorker, parallelServerBuildTraces, parallelServerCompiles + +**Styling:** +- Tailwind CSS 3.4.17 - Utility-first CSS framework + - Animation plugin: `tailwindcss-animate` 1.0.7 + - Config: `tailwind.config.ts` + - CSS Variables enabled for theming (light/dark mode) + +**UI Components:** +- Radix UI (15+ component libraries) - Headless, accessible primitives + - Core: accordion, alert-dialog, avatar, checkbox, dialog, dropdown-menu, label, popover, select, tabs, toast, tooltip, etc. + - shadcn/ui style components (local copies, not npm packages) + - Config: `components.json` + - Aliases: `@/components`, `@/components/ui`, `@/lib`, `@/hooks` + +**Form Management:** +- React Hook Form (latest) - Lightweight form state management +- Zod (latest) - Schema validation and type-safe form schemas +- @hookform/resolvers (latest) - Integration between React Hook Form and validation libraries + +**Data Visualization:** +- Recharts (latest) - React charting library built on D3.js + - Used for dashboard analytics/metrics visualization + +**Notifications & Toasts:** +- Sonner 1.7.1 - Toast notification library +- Radix UI Toast 1.2.4 - Low-level toast primitive (wrapped by use-toast) + +**Navigation & Theming:** +- next-themes 0.4.4 - Dark/light mode switching +- lucide-react 0.454.0 - Icon library (240+ icons) + +**HTTP Client:** +- Axios 1.9.0 - Promise-based HTTP client + - Configured with request/response interceptors in `lib/api/client.ts` + - Automatic token injection via Authorization header + - 401 error handling (token expiry redirect to /login) + +**Utilities:** +- js-cookie 3.0.5 - Cookie management (auth token persistence) +- date-fns 4.1.0 - Date utility library +- class-variance-authority 0.7.1 - Component variant utilities +- clsx 2.1.1 - Conditional className merging +- tailwind-merge 2.5.5 - Tailwind class conflict resolution +- cmdk 1.0.4 - Command palette component +- embla-carousel-react 8.5.1 - Carousel component +- react-day-picker 8.10.1 - Calendar/date picker +- input-otp 1.4.1 - OTP input component +- react-resizable-panels 2.1.7 - Resizable panel layout +- vaul 0.9.6 - Drawer component + +## Testing & Development + +**Type Checking:** +- TypeScript 5 (strict mode enabled) +- Type checking via `npm run lint` + +**Linting:** +- Next.js ESLint (build-time linting) +- Config: ignored during builds (`eslint.ignoreDuringBuilds: true`) + +**Development Server:** +- Next.js dev server: `npm run dev` (default port 3000) + +## Configuration + +**Environment:** +- Location: `.env.local` (local overrides), `.env.development`, `.env.production`, `.env.example` +- Example file: `c:\Users\admin\Desktop\Lila-Server\qy-lty-admin\.env.example` +- Key env vars: + - `NEXT_PUBLIC_API_BASE_URL` - Backend API base URL (e.g., `http://localhost:8000/api`) + - `NODE_ENV` - Set to `production` in Docker runtime + +**TypeScript:** +- Config: `tsconfig.json` +- Strict mode: enabled +- Path aliases: `@/*` maps to project root + +**Next.js:** +- Config: `next.config.mjs` +- Output mode: `standalone` (self-contained app) +- Images: unoptimized (external image optimization) +- Build optimization: parallel webpack, server build traces, server compilation + +## Platform Requirements + +**Development:** +- Node.js 22+ (or compatible version) +- Package manager: yarn, npm, or pnpm +- Environment variables: `.env.local` with `NEXT_PUBLIC_API_BASE_URL` + +**Production:** +- Docker runtime: Node.js 22.10.0 Alpine +- Memory: Typical SPA requirements (~500MB minimum) +- Port: 3000 (exposed in Dockerfile) +- Build artifacts: `.next/standalone`, `public/` + +## Build Process + +**Development Build:** +```bash +npm install # Install dependencies +npm run dev # Start dev server (hot reload) +``` + +**Production Build:** +```bash +npm install # Install all dependencies +npm run build # Next.js standalone build → .next/ directory +npm run start # Start production server (requires .next/ + public/) +``` + +**Docker Build (CI/CD):** +- Multi-stage build (builder + runner) +- Builder stage: installs full dependencies, runs `npm run build` +- Runner stage: installs production-only dependencies, copies .next/ and public/ +- Yarn mirror: Taobao registry (`registry.npmmirror.com`) +- Final CMD: `yarn start` (port 3000) + +--- + +*Stack analysis: 2026-05-07* diff --git a/qy-lty-admin/.planning/codebase/STRUCTURE.md b/qy-lty-admin/.planning/codebase/STRUCTURE.md new file mode 100644 index 0000000..93250f3 --- /dev/null +++ b/qy-lty-admin/.planning/codebase/STRUCTURE.md @@ -0,0 +1,291 @@ +# Codebase Structure + +**Analysis Date:** 2026-05-07 + +## Directory Layout + +``` +qy-lty-admin/ +├── app/ # Next.js App Router pages & layouts +│ ├── layout.tsx # Root layout with metadata +│ ├── page.tsx # Dashboard homepage +│ ├── login/page.tsx # Login page +│ ├── register/page.tsx # Registration page +│ ├── forgot-password/page.tsx # Password reset page +│ ├── ai-model/page.tsx # AI Model management +│ ├── outfits/ # Outfit content module +│ │ ├── page.tsx # List view +│ │ ├── [id]/page.tsx # Detail view +│ │ ├── edit/[id]/page.tsx # Edit form +│ │ └── loading.tsx # Loading skeleton +│ ├── props/ # Props module (same structure as outfits) +│ ├── home-decor/ # Home decor module +│ ├── food/ # Food module +│ ├── songs/ # Songs module +│ ├── dances/ # Dances module +│ ├── achievements/ # Achievements module +│ ├── affinity/ # Affinity/favorability module +│ ├── users/ # User management module +│ ├── permissions/ # Permissions management module +│ ├── settings/ # System settings module +│ └── globals.css # Global Tailwind + custom styles +├── components/ # Reusable React components +│ ├── ui/ # Shadcn-style primitive components (copy-paste) +│ │ ├── button.tsx +│ │ ├── card.tsx +│ │ ├── dialog.tsx +│ │ ├── input.tsx +│ │ ├── label.tsx +│ │ ├── table.tsx +│ │ ├── tabs.tsx +│ │ ├── select.tsx +│ │ ├── form.tsx +│ │ ├── alert-dialog.tsx +│ │ └── ... (30+ primitive components) +│ ├── outfits/ # Outfit feature components +│ │ ├── OutfitsList.tsx +│ │ ├── OutfitsTable.tsx +│ │ ├── AddOutfitDialog.tsx +│ │ └── OutfitDetailCard.tsx +│ ├── songs/ # Songs feature components +│ ├── dances/ # Dances feature components +│ ├── food/ # Food feature components +│ ├── home-decor/ # Home decor feature components +│ ├── props/ # Props feature components +│ ├── affinity/ # Affinity feature components +│ ├── achievements/ # Achievements feature components +│ ├── permissions/ # Permissions feature components +│ ├── users/ # Users feature components +│ ├── sidebar.tsx # Main navigation sidebar (client) +│ ├── dashboard-shell.tsx # Layout wrapper with permission checks (client) +│ ├── dashboard-header.tsx # Page header component +│ ├── overview.tsx # Dashboard overview chart +│ ├── recent-activity.tsx # Recent activity feed +│ ├── stat-card.tsx # KPI stat card +│ ├── delete-confirmation-dialog.tsx +│ ├── publish-confirmation-dialog.tsx +│ ├── theme-provider.tsx +│ └── add-outfit-dialog.tsx # Shared modal for outfit creation +├── lib/ # Utilities, types, API clients +│ ├── api/ +│ │ ├── client.ts # Axios instance + interceptors + interfaces +│ │ ├── auth.ts # Login, logout, token management +│ │ ├── outfits.ts # Outfit CRUD operations +│ │ ├── props.ts # Props CRUD operations +│ │ ├── songs.ts # Songs CRUD operations +│ │ ├── dances.ts # Dances CRUD operations +│ │ ├── food.ts # Food CRUD operations +│ │ ├── home-decor.ts # Home decor CRUD operations +│ │ ├── achievements.ts # Achievements CRUD operations +│ │ ├── affinity.ts # Affinity system API +│ │ ├── ai-models.ts # AI model management +│ │ ├── users.ts # User management +│ │ ├── roles.ts # Role/permission management +│ │ ├── error-handler.ts # Centralized error handling +│ │ ├── adapters.ts # Response schema mappers +│ │ ├── types.ts # Shared API type definitions +│ │ ├── token-debug.ts # Token debugging utilities +│ │ ├── card.ts # Card-related API +│ │ └── index.ts # Export barrel +│ ├── permissions.ts # RBAC matrix + permission utilities +│ └── utils.ts # Utility functions (Tailwind cn) +├── hooks/ # Custom React hooks +│ ├── use-mobile.tsx # Mobile breakpoint detection hook +│ └── use-toast.ts # Toast notification hook +├── middleware.ts # Next.js route protection middleware +├── public/ # Static assets +├── styles/ # Additional stylesheets (if any) +├── package.json # Dependencies & scripts +├── tsconfig.json # TypeScript configuration +├── tailwind.config.ts # Tailwind CSS configuration +├── postcss.config.mjs # PostCSS configuration +├── next.config.mjs # Next.js configuration +├── Dockerfile # Docker build (yarn + Taobao mirror) +├── docker-compose.yml # Local dev Docker Compose +├── README.md # Project overview & permissions matrix +├── CLAUDE.md # Codebase instructions for Claude +└── docs/ + └── 修改记录.md # Change log (Chinese) +``` + +## Directory Purposes + +**app/:** +- Purpose: Next.js App Router pages and layouts; each module has a folder with page.tsx + optional nested routes +- Contains: Page components ("use client"), form pages, dynamic routes with [id] +- Key files: `page.tsx` (list), `[id]/page.tsx` (detail), `edit/[id]/page.tsx` (edit form), `loading.tsx` (skeleton) + +**components/:** +- Purpose: Reusable React components organized by feature +- Contains: UI primitives (components/ui/), feature-specific components (components/[feature]/), layout components (sidebar, shell) +- Key pattern: Feature folders mirror app module structure; components within are sub-components (no subfolders unless complex) + +**components/ui/:** +- Purpose: Shadcn-style copy-paste UI primitives +- Contains: 30+ Base UI components (Button, Card, Dialog, Form, Table, Tabs, Select, etc.) +- Note: These are copied into the repo, not npm packages; safe to modify directly + +**lib/api/:** +- Purpose: HTTP client and API communication layer +- Contains: Axios singleton instance, interceptors, module-specific API functions, response adapters, type definitions +- Pattern: One file per backend module (outfits.ts, songs.ts, etc.) + shared client.ts + +**lib/:** +- Purpose: Shared utilities, types, configuration +- Contains: permissions.ts (RBAC logic), utils.ts (Tailwind helpers), api/ subfolder + +**hooks/:** +- Purpose: Custom React hooks +- Contains: use-mobile (responsive detection), use-toast (notification system) + +**middleware.ts:** +- Purpose: Next.js request middleware for route protection +- Functionality: Checks Cookie auth_token; redirects to /login if missing; whitelists public routes + +**public/:** +- Purpose: Static assets (images, icons, etc.) + +**styles/:** +- Purpose: Additional CSS if needed (most styling via Tailwind in components) + +## Key File Locations + +**Entry Points:** +- `app/layout.tsx`: Root layout (metadata only) +- `app/page.tsx`: Dashboard homepage (stats, charts, links) +- `app/login/page.tsx`: Authentication entry point +- `middleware.ts`: Route protection middleware + +**Configuration:** +- `next.config.mjs`: Next.js config (standalone output) +- `tsconfig.json`: TypeScript strict mode +- `tailwind.config.ts`: Tailwind CSS theme + animations +- `postcss.config.mjs`: PostCSS with Tailwind + autoprefixer +- `.env.local`: Environment variables (API_BASE_URL) + +**Core Logic:** +- `lib/permissions.ts`: Role-based access control matrix & utilities +- `lib/api/client.ts`: Axios instance with auth/error interceptors +- `components/dashboard-shell.tsx`: Permission-checking layout wrapper +- `components/sidebar.tsx`: Role-filtered navigation menu + +**Authentication:** +- `lib/api/auth.ts`: Login, logout, token persistence +- `middleware.ts`: Route-level token validation + +**Testing:** +- No test files detected; testing not yet set up + +## Naming Conventions + +**Files:** +- **Page components:** `page.tsx` (App Router standard) +- **Feature pages:** `/app/[module]/page.tsx` (e.g., app/outfits/page.tsx) +- **Dynamic routes:** `/app/[module]/[id]/page.tsx` (e.g., app/outfits/[id]/page.tsx) +- **Layout files:** `layout.tsx` +- **Loading states:** `loading.tsx` +- **API modules:** `/lib/api/[feature].ts` (e.g., lib/api/outfits.ts) +- **Component files:** PascalCase (e.g., OutfitsList.tsx, AddOutfitDialog.tsx) +- **Hook files:** `use-[name].ts` (e.g., use-mobile.tsx) + +**Directories:** +- **App modules:** kebab-case (e.g., `/app/ai-model`, `/app/home-decor`, `/app/forgot-password`) +- **Component folders:** kebab-case for feature groups, `ui` for primitives +- **API folder:** All in `lib/api/` (no nested folders) + +**Functions:** +- **API functions:** camelCase, verb-first (e.g., `getOutfits()`, `createOutfit()`, `updateOutfit()`) +- **Utility functions:** camelCase (e.g., `hasPermission()`, `getUserRole()`) +- **Components:** PascalCase (e.g., `DashboardShell`, `Sidebar`, `OutfitsList`) +- **Hooks:** camelCase with `use` prefix (e.g., `useMobile`, `useToast`) + +**Variables/Constants:** +- **Type definitions:** PascalCase (e.g., `Outfit`, `RoleName`, `ApiResponse`) +- **Constants:** UPPER_SNAKE_CASE (e.g., `API_BASE_URL`, `PERMISSION_MATRIX`) +- **Local variables:** camelCase (e.g., `outfits`, `isLoading`, `userRole`) + +**Routes:** +- **Module routes:** kebab-case URLs (e.g., `/ai-model`, `/home-decor`, `/forgot-password`) +- **Dynamic routes:** `[paramName]` (e.g., `/outfits/[id]`, `/dances/[id]/edit`) + +## Where to Add New Code + +**New Feature Module (e.g., New Content Type):** + +1. **Page component:** Create `app/[module-name]/page.tsx` + - Wrap with DashboardShell + - Import feature components from `components/[module-name]/` + - Call API functions from `lib/api/[module-name].ts` + +2. **API client:** Create `lib/api/[module-name].ts` + - Export `get[Feature]s()`, `get[Feature]()`, `create[Feature]()`, `update[Feature]()`, `delete[Feature]()` + - Use `apiClient.get/post/put/delete()` from `lib/api/client.ts` + - Include adapter function to map backend response to frontend type + - Export types or import from `lib/api/types.ts` + +3. **Components:** Create `components/[module-name]/` folder + - List component (e.g., `[Feature]List.tsx`) + - Detail/edit component + - Dialog/modal for creation + - Table component if displaying tabular data + +4. **Permission matrix:** Add to `lib/permissions.ts` + - Add module key to `PermissionModule` type + - Add module to relevant roles in `PERMISSION_MATRIX` + - Add path mapping in `getModuleFromPath()` + +5. **Sidebar:** Update `components/sidebar.tsx` + - Add menu item to appropriate section (AI Admin, Content Admin, or System Admin) + - Use lucide-react icon + +6. **Middleware:** Update `middleware.ts` + - Add route to `protectedPaths` array if requiring auth + +**New Component:** +- Location: `components/[feature-name]/[ComponentName].tsx` +- Import from UI primitives: `components/ui/button.tsx`, etc. +- Use React Hook Form for forms +- Use icons from lucide-react +- Mark as "use client" if using hooks/state + +**New Utility Function:** +- Location: `lib/utils.ts` (for generic utilities) or `lib/api/[module].ts` (for API helpers) +- Type everything with TypeScript +- Export from `lib/api/index.ts` if API-related + +**New Hook:** +- Location: `hooks/use-[name].ts` +- Follow React hook naming convention (use- prefix) + +## Special Directories + +**components/ui/:** +- Purpose: Shadcn-style primitives (Button, Card, Dialog, Form, Input, etc.) +- Generated: No (manually copied from shadcn/ui) +- Committed: Yes (safe to modify without breaking npm upgrades) +- Note: Each file is a single component; complex components like Dialog may have sub-exports + +**public/:** +- Purpose: Static assets served at root level +- Generated: No +- Committed: Yes + +**node_modules/:** +- Purpose: Installed dependencies +- Generated: Yes (by npm/pnpm/yarn) +- Committed: No (.gitignore) + +**.next/:** +- Purpose: Next.js build output +- Generated: Yes (by `npm run build`) +- Committed: No (.gitignore) + +**lib/api/:** +- Purpose: All API communication in one folder for easy discovery +- Generated: No (hand-written) +- Committed: Yes +- Note: No subfolders; keep flat for single-module-per-file pattern + +--- + +*Structure analysis: 2026-05-07* diff --git a/qy-lty-admin/.planning/codebase/TESTING.md b/qy-lty-admin/.planning/codebase/TESTING.md new file mode 100644 index 0000000..da1f14f --- /dev/null +++ b/qy-lty-admin/.planning/codebase/TESTING.md @@ -0,0 +1,279 @@ +# 测试模式 + +**分析日期:** 2026-05-07 + +## 测试框架 + +**当前状态:** 未检测到测试基础设施 + +- **运行器:** 未配置(package.json 中无 Jest、Vitest、Playwright 等依赖) +- **断言库:** 未配置 +- **测试脚本:** 未在 package.json 中定义(仅有 `dev`、`build`、`start`、`lint`) + +## 测试文件组织 + +**当前:** 无测试文件 + +项目中未发现 `*.test.ts`、`*.test.tsx`、`*.spec.ts`、`*.spec.tsx`、`__tests__/` 目录等标准测试文件结构。 + +## 测试覆盖 + +**要求:** 未强制 + +**影响:** +- 新增代码(尤其是 API 集成、表单验证、权限逻辑)缺乏自动化验证 +- 技术债风险:`next.config.mjs` 中 `typescript.ignoreBuildErrors: true` 和 `eslint.ignoreDuringBuilds: true`,导致构建时不检查类型和 lint 错误 +- 手动测试依赖性高 + +## 关键需要测试的区域 + +### API 集成层 +**位置:** `lib/api/*.ts` + +**建议测试:** +- 认证流程(`emailLogin`、`saveAuthToken`、`logout`) +- API 请求拦截器(token 注入、过期处理) +- 响应拦截器(401 处理、重定向) +- 数据适配器(如 `mapBackendOutfit()` 将后端结构映射到前端类型) +- 错误处理(`handleApiException`、`handleApiRequest` 的成功/失败路径) + +**示例测试需求:** +```typescript +// 应测试:token 自动注入 +test('请求拦截器应在请求头中添加 Bearer token', () => { + // 前置条件:localStorage 中有 auth_token + // 发送 API 请求 + // 验证:Authorization 头包含 "Bearer " +}) + +// 应测试:401 处理 +test('响应拦截器应在收到 401 时清除 token 并重定向到登录', () => { + // 前置条件:发送请求后服务器返回 401 + // 验证:localStorage 中 auth_token 被移除 + // 验证:浏览器重定向到 /login +}) + +// 应测试:数据适配 +test('mapBackendOutfit 应正确转换后端服装数据', () => { + const backendData = { id: 1, name: '测试服装', image_url: '...' } + const result = mapBackendOutfit(backendData) + expect(result).toHaveProperty('imageUrl') // 字段映射 + expect(result.id).toBe('1') // 类型转换 +}) +``` + +### 权限控制 +**位置:** `lib/permissions.ts` + +**建议测试:** +- `hasPermission(module)` 根据用户角色返回正确的权限 +- `getUserRole()` 从 localStorage 获取当前角色 +- `getModuleFromPath(pathname)` 正确提取路径中的模块 +- `hasPathPermission(pathname)` 组合权限检查 + +**示例测试需求:** +```typescript +test('超级管理员应有所有模块的权限', () => { + // 前置:localStorage.user_role = '超级管理员' + // 验证:hasPermission('users') === true + // 验证:hasPermission('ai-model') === true + // 等等所有模块 +}) + +test('内容管理员应无权限访问用户管理', () => { + // 前置:localStorage.user_role = '内容管理员' + // 验证:hasPermission('users') === false + // 验证:hasPermission('outfits') === true +}) +``` + +### 表单验证与提交 +**位置:** `components/*/add-*-dialog.tsx`、`app/*/page.tsx` + +**当前模式:** +- 使用 `useState` 管理表单状态 +- 基本的非空检查(如 `if (!formData.name || !formData.description)` 在 `add-achievement-dialog.tsx`) +- 客户端验证不完整 + +**建议测试:** +- 必填字段验证(空值拒绝) +- 表单提交成功调用正确的 API +- 提交过程中加载状态变化(`isSubmitting`) +- 表单重置(关闭后清空状态) +- 错误显示(API 失败时显示 toast) + +**示例测试需求:** +```typescript +test('添加服装对话框应在名称为空时禁用提交', () => { + // 渲染 AddOutfitDialog + // 验证:提交按钮禁用状态 +}) + +test('提交表单应调用 createOutfit API', () => { + // 填写表单字段 + // 点击提交 + // 验证:createOutfit 被调用,参数正确 +}) +``` + +### 组件交互 +**位置:** `components/` + +**建议测试:** +- 对话框打开/关闭状态 +- 模态框表单的步骤导航(`step` 状态变化) +- 确认对话框的确认/取消操作 +- 列表分页和搜索 + +**示例测试需求:** +```typescript +test('AddOutfitDialog 应在提交后关闭', () => { + // 渲染对话框 + // 填写并提交表单 + // 验证:对话框关闭(open === false) +}) + +test('多步表单应在点击"下一步"时更新步骤', () => { + // 验证:初始 step === 1 + // 点击"下一步"按钮 + // 验证:step === 2 +}) +``` + +### 中间件和路由保护 +**位置:** `middleware.ts` + +**建议测试:** +- 无 token 访问受保护路由时重定向到 `/login` +- 有 token 时允许访问受保护路由 +- 公共路由(登录、注册)不需要 token + +**示例测试需求:** +```typescript +test('中间件应将无 token 的请求重定向到登录', () => { + // 模拟请求:GET /outfits,无 auth_token cookie + // 验证:重定向到 /login?callbackUrl=/outfits +}) + +test('中间件应允许有 token 的请求', () => { + // 模拟请求:GET /users,包含有效 auth_token cookie + // 验证:通过,继续处理 +}) +``` + +## 建议的测试设置 + +### 推荐框架组合 + +**单元/集成测试:** +- **运行器:** Vitest(与 Next.js 15 兼容,开发体验优于 Jest) +- **断言:** Vitest 内置(或 @testing-library/jest-dom) +- **Mock 库:** vitest 的 `vi` 模块或 `@testing-library/react` + +**E2E 测试(可选):** +- **框架:** Playwright 或 Cypress +- **用途:** 完整登录流、权限控制、多步表单 + +### 安装建议 + +```bash +# 安装 Vitest 和相关工具 +npm install -D vitest @vitest/ui @testing-library/react @testing-library/jest-dom + +# 创建配置文件 vitest.config.ts +``` + +### 最小化配置示例 + +```typescript +// vitest.config.ts +import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react' +import path from 'path' + +export default defineConfig({ + plugins: [react()], + test: { + globals: true, + environment: 'jsdom', + setupFiles: ['./test/setup.ts'], + }, + resolve: { + alias: { + '@': path.resolve(__dirname, '.'), + }, + }, +}) + +// test/setup.ts +import '@testing-library/jest-dom' +``` + +### package.json 脚本建议 + +```json +{ + "scripts": { + "test": "vitest", + "test:ui": "vitest --ui", + "test:coverage": "vitest --coverage", + "test:run": "vitest run" + } +} +``` + +## 测试文件结构建议 + +``` +project-root/ +├── lib/ +│ ├── api/ +│ │ ├── auth.ts +│ │ ├── auth.test.ts # API 认证测试 +│ │ ├── outfits.ts +│ │ ├── outfits.test.ts # API 服装测试 +│ │ └── ... +│ ├── permissions.ts +│ └── permissions.test.ts # 权限逻辑测试 +├── components/ +│ ├── add-outfit-dialog.tsx +│ ├── add-outfit-dialog.test.tsx # 组件交互测试 +│ └── ... +├── app/ +│ ├── login/ +│ │ ├── page.tsx +│ │ └── page.test.tsx # 登录页测试 +│ └── ... +├── test/ +│ ├── setup.ts # 测试环境初始化 +│ ├── mocks/ +│ │ ├── handlers.ts # MSW 请求拦截器 +│ │ └── server.ts # MSW 服务器配置 +│ └── fixtures/ +│ ├── auth.fixture.ts # 认证数据 fixtures +│ └── outfits.fixture.ts # 服装数据 fixtures +└── vitest.config.ts +``` + +## 当前缺失的覆盖范围 + +**高优先级(生产关键):** +- ✗ API 集成和数据适配 +- ✗ 认证流程(登录、token 管理、登出) +- ✗ 权限检查和路由保护 +- ✗ 表单验证和提交 + +**中优先级(功能完整性):** +- ✗ 错误处理和用户通知 +- ✗ 组件交互(对话框、模态框) +- ✗ 列表管理(分页、搜索) + +**低优先级(UX 细节):** +- ✗ 样式和响应性 +- ✗ 无障碍访问 (a11y) + +--- + +*测试分析日期:2026-05-07* + +**关键建议:** 该项目缺乏测试基础设施,强烈建议在新增核心功能(特别是认证、权限、API 集成)时同步添加测试。考虑从 API 层和权限控制开始,这些是最容易产生 bug 的关键区域。