# 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*