# MemeStudio Server Project - Comprehensive Analysis ## 1. PROJECT OVERVIEW **Project Name**: Meme Studio (谐音梗小游戏运营平台) **Description**: A homophone pun game operation platform built with Next.js 14 for level configuration management **Current Version**: 0.1.0 **Key Purpose**: Management platform for game levels, users, and mini-program users --- ## 2. FRAMEWORK & TECH STACK ### Core Framework - **Next.js**: v14.2.28 (App Router) - **Runtime**: Node.js (Standalone output mode) - **Language**: TypeScript 5.8.2 - **Build Output**: `output: 'standalone'` (for server deployment) ### Authentication & Database - **Auth System**: Better Auth v1.2.7 - Method: Email/Password with Prisma adapter - Session Duration: 7 days - Session Update Age: 1 day - Database Provider: MySQL via Prisma - **ORM**: Prisma v6.5.0 - **Database**: MySQL - **Adapter**: Better Auth Prisma adapter ### UI & Styling - **UI Components**: shadcn/ui (Radix UI based) - **CSS Framework**: Tailwind CSS v3.4.17 - **Form Handling**: react-hook-form v7.54.2 + Zod validation - **State Management**: TanStack React Query v5.69.0 - **Drag & Drop**: @dnd-kit (core v6.3.1 + sortable v10.0.0) - **Icons**: lucide-react v0.483.0 ### File Storage & CDN - **Cloud Storage**: Tencent Cloud COS (Object Storage Service) - **SDK**: - qcloud-cos-sts v3.1.1 (For temporary credentials) - cos-nodejs-sdk-v5 v2.14.0 (Server-side) - cos-js-sdk-v5 v1.10.1 (Client-side) ### Utilities - **UUID Generation**: uuid v11.1.0 - **Password Hashing**: bcryptjs v3.0.2 + better-auth/crypto - **Form Validation**: Zod v3.24.2 --- ## 3. PROJECT STRUCTURE ``` MemeStudio/ ├── app/ # Next.js App Router │ ├── (auth)/ # Route group: No sidebar (login page) │ │ └── login/ │ │ └── page.tsx │ ├── (dashboard)/ # Route group: With sidebar (protected) │ │ ├── layout.tsx │ │ ├── levels/ │ │ │ └── page.tsx │ │ ├── users/ │ │ │ └── page.tsx │ │ └── wx-users/ │ │ └── page.tsx │ ├── api/ # API Routes │ │ ├── auth/[...all]/ │ │ │ └── route.ts # Better Auth endpoints │ │ ├── levels/ │ │ │ ├── route.ts # CRUD endpoints │ │ │ └── reorder/ │ │ │ └── route.ts # Batch update sort order │ │ ├── cos/ │ │ │ └── temp-key/ │ │ │ └── route.ts # Tencent COS credentials │ │ ├── users/ │ │ │ └── route.ts # User management CRUD │ │ └── wx-users/ │ │ ├── route.ts # Mini-program user list │ │ ├── [id]/ │ │ │ └── route.ts # Single user details │ │ └── level-progress/ │ │ └── route.ts # Batch delete progress │ ├── layout.tsx # Root layout │ ├── page.tsx # Home page (redirects to /levels) │ └── providers.tsx # Client providers │ ├── components/ # React Components │ ├── layout/ │ │ ├── header.tsx # Header with session info │ │ └── sidebar.tsx # Navigation sidebar │ ├── levels/ │ │ ├── level-list.tsx # Drag-and-drop list │ │ ├── level-card.tsx # Individual level card │ │ ├── level-dialog.tsx # Create/edit dialog │ │ └── image-uploader.tsx # COS image upload │ ├── users/ │ │ └── user-dialog.tsx # User create/edit dialog │ ├── wx-users/ │ │ └── wx-user-detail-dialog.tsx # Mini-program user details │ └── ui/ # shadcn/ui components │ ├── button.tsx │ ├── input.tsx │ ├── label.tsx │ ├── dialog.tsx │ ├── card.tsx │ ├── textarea.tsx │ └── spinner.tsx │ ├── lib/ # Utilities & Helpers │ ├── auth.ts # Better Auth configuration │ ├── auth-client.ts # Client-side auth hooks │ ├── prisma.ts # Prisma singleton │ ├── cos.ts # Tencent COS utilities │ ├── api.ts # apiFetch helper with basePath │ └── utils.ts # General utilities (cn for Tailwind) │ ├── types/ # TypeScript interfaces │ └── index.ts # All data models │ ├── prisma/ │ ├── schema.prisma # Database schema │ └── seed.ts # Seed script for admin user │ ├── public/ # Static assets ├── middleware.ts # Session validation middleware ├── next.config.js # Next.js configuration ├── tsconfig.json # TypeScript config ├── tailwind.config.ts # Tailwind CSS config ├── package.json # Dependencies ├── deploy.sh # Deployment script └── ecosystem.config.js # PM2 configuration ``` --- ## 4. DATABASE SCHEMA (Prisma) ### Models Overview #### 1. **Level** (Game Levels) ```prisma model Level { id String @id @default(uuid()) imageUrl String @map("image_url") answer String hint1 String? hint2 String? hint3 String? sortOrder Int @default(0) @map("sort_order") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") userProgress WxUserLevelProgress[] # Relationship to progress } ``` - Stores game level information - Each level has one image, one answer, and up to 3 hints - `sortOrder` is used for drag-and-drop reordering - Related to WxUserLevelProgress (one-to-many) #### 2. **User** (Admin/Portal Users) ```prisma model User { id String @id @default(uuid()) email String @unique emailVerified Boolean @default(false) name String? image String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt sessions Session[] accounts Account[] } ``` - Better Auth user model - Email-password authentication - Can have multiple sessions and accounts #### 3. **Session** (Auth Sessions) ```prisma model Session { id String @id expiresAt DateTime token String @unique ipAddress String? userAgent String? userId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) } ``` - Better Auth session model - Stores session tokens with IP and user agent info - 7-day expiration #### 4. **Account** (Auth Accounts) ```prisma model Account { id String @id @default(uuid()) accountId String providerId String userId String accessToken String? refreshToken String? idToken String? accessTokenExpires DateTime? refreshTokenExpires DateTime? scope String? password String? # Hash for email/password provider createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) } ``` - Better Auth account model - Supports multiple auth providers - Stores hashed passwords for credential provider #### 5. **Verification** (Email Verification) ```prisma model Verification { id String @id @default(uuid()) identifier String value String expiresAt DateTime createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } ``` - Better Auth verification model for email verification tokens #### 6. **WxUser** (Mini-Program Users) ```prisma model WxUser { id String @id @default(uuid()) openid String @unique sessionKey String? nickname String? avatarUrl String? points Int @default(10) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt levelProgress WxUserLevelProgress[] } ``` - Users from WeChat mini-program - OpenID uniquely identifies users (WeChat standard) - Stores user points/score - Related to level progress #### 7. **WxUserLevelProgress** (User Progress Tracking) ```prisma model WxUserLevelProgress { id String @id @default(uuid()) userId String levelId String completedAt DateTime @default(now()) user WxUser @relation(fields: [userId], references: [id], onDelete: Cascade) level Level @relation(fields: [levelId], references: [id]) } ``` - Tracks which levels each WeChat user has completed - Stores completion timestamp - Composite relationship between WxUser and Level ### Database Configuration - **Provider**: MySQL - **Connection String**: Defined via `DATABASE_URL` env var - **Targets**: `["native", "rhel-openssl-3.0.x"]` for cross-platform compatibility (macOS dev → Linux server) --- ## 5. API ROUTES - COMPREHENSIVE DOCUMENTATION ### Authentication Routes #### `POST /api/auth/[...all]` - **Handler**: Better Auth routes - **Auth**: Yes (Better Auth handles internally) - **Purpose**: All authentication endpoints - **Endpoints Provided**: - `POST /api/auth/sign-up` - User registration - `POST /api/auth/sign-in` - User login - `POST /api/auth/sign-out` - User logout - `GET /api/auth/session` - Get current session - etc. (all Better Auth endpoints) ### Levels Management #### `GET /api/levels` - **Auth**: Required (401 if unauthorized) - **Purpose**: Fetch all game levels - **Query Params**: None - **Returns**: Array of Level objects, ordered by sortOrder ASC - **Response**: ```json [ { "id": "uuid", "imageUrl": "https://...", "answer": "答案", "hint1": "提示1", "hint2": null, "hint3": null, "sortOrder": 0, "createdAt": "2026-04-05T...", "updatedAt": "2026-04-05T..." } ] ``` #### `POST /api/levels` - **Auth**: Required - **Purpose**: Create a new game level - **Request Body**: ```json { "imageUrl": "string (required)", "answer": "string (required)", "hint1": "string (optional)", "hint2": "string (optional)", "hint3": "string (optional)" } ``` - **Logic**: - Gets max sortOrder and sets new level to max+1 - Uses UUID for ID - Validates required fields (imageUrl, answer) - **Returns**: Created Level object (201) #### `PUT /api/levels` - **Auth**: Required - **Purpose**: Update an existing level - **Request Body**: ```json { "id": "string (required)", "imageUrl": "string", "answer": "string", "hint1": "string", "hint2": "string", "hint3": "string" } ``` - **Logic**: Direct update via Prisma - **Returns**: Updated Level object #### `DELETE /api/levels` - **Auth**: Required - **Purpose**: Delete a level - **Query Params**: `id` (string, required) - **Returns**: `{ "success": true }` #### `PUT /api/levels/reorder` - **Auth**: Required - **Purpose**: Batch update sort order for drag-and-drop - **Request Body**: ```json { "orders": [ { "id": "level-id", "sortOrder": 0 }, { "id": "level-id", "sortOrder": 1 } ] } ``` - **Logic**: Uses Prisma transaction to update all in parallel - **Returns**: `{ "success": true }` ### Users Management (Admin/Portal Users) #### `GET /api/users` - **Auth**: Required - **Purpose**: Fetch all portal users - **Query Params**: None - **Returns**: Array of User objects (ordered by createdAt DESC) - **Excludes**: Password hash is not returned #### `POST /api/users` - **Auth**: Required - **Purpose**: Create a new portal user - **Request Body**: ```json { "email": "string (required)", "password": "string (required)", "name": "string (optional)" } ``` - **Logic**: - Checks if email already exists (error: "该邮箱已被注册") - Hashes password using `better-auth/crypto` - Creates User + Account in transaction - Account has providerId="credential" - **Returns**: Created User object (201) #### `PUT /api/users` - **Auth**: Required - **Purpose**: Update a portal user - **Request Body**: ```json { "id": "string (required)", "email": "string (optional)", "password": "string (optional)", "name": "string (optional)" } ``` - **Logic**: - Validates email not taken by another user - Updates password if provided (hashing in transaction) - Uses transaction for consistency - **Returns**: Updated User object #### `DELETE /api/users` - **Auth**: Required - **Purpose**: Delete a portal user - **Query Params**: `id` (string, required) - **Logic**: - Prevents deleting yourself (checks session.user.id) - Error: "不能删除自己的账户" - **Returns**: `{ "success": true }` ### Mini-Program Users #### `GET /api/wx-users` - **Auth**: Required - **Purpose**: Get WeChat mini-program users with pagination and search - **Query Params**: - `search` - Search in nickname or openid (optional) - `page` - Page number (default: 1) - `limit` - Results per page (default: 20) - **Logic**: - Search: OR condition on nickname.contains and openid.contains - Returns paginated results with metadata - **Returns**: ```json { "users": [ { "id": "uuid", "openid": "string", "nickname": "string", "avatarUrl": "string", "points": 10, "createdAt": "2026-04-05T...", "updatedAt": "2026-04-05T..." } ], "meta": { "total": 100, "page": 1, "limit": 20, "totalPages": 5 } } ``` - **Performance**: Uses `select` to exclude sessionKey #### `GET /api/wx-users/[id]` - **Auth**: Required - **Purpose**: Get single WeChat user with level progress history - **Params**: `id` - User UUID - **Returns**: ```json { "id": "uuid", "openid": "string", "nickname": "string", "avatarUrl": "string", "points": 10, "createdAt": "2026-04-05T...", "updatedAt": "2026-04-05T...", "levelProgress": [ { "id": "uuid", "userId": "uuid", "levelId": "uuid", "completedAt": "2026-04-05T...", "level": { "id": "uuid", "answer": "答案" } } ] } ``` - **Logic**: Includes levelProgress ordered by completedAt DESC #### `DELETE /api/wx-users/level-progress` - **Auth**: Required - **Purpose**: Batch delete level progress records - **Request Body**: ```json { "ids": ["progress-id-1", "progress-id-2"] } ``` - **Logic**: - Validates ids is non-empty array of strings - Uses deleteMany with IN clause - **Returns**: `{ "deleted": 5 }` ### COS (Tencent Cloud Object Storage) #### `GET /api/cos/temp-key` - **Auth**: Required - **Purpose**: Get temporary credentials for frontend image upload - **Query Params**: None - **Logic**: - Uses Tencent COS STS service - Generates temp credentials valid for 30 minutes (1800 seconds) - Restricts upload to `mini_game/images/*` prefix - **Returns**: ```json { "credentials": { "tmpSecretId": "string", "tmpSecretKey": "string", "sessionToken": "string" }, "startTime": 1712345678, "expiredTime": 1712347478, "bucket": "lookai-1308511832", "region": "ap-guangzhou" } ``` ### Authentication Patterns - **Session Check Pattern**: ```typescript const session = await auth.api.getSession({ headers: request.headers }) if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) ``` - **All routes use the same pattern** (except Better Auth routes) - **No role/permission system yet** - all authenticated users have full access --- ## 6. MIDDLEWARE & AUTHENTICATION ### File: `middleware.ts` **Location**: Root level **Purpose**: Session validation for protected pages **Features**: - Checks for session cookies on protected pages - Handles both HTTP and HTTPS cookie naming: - `better-auth.session_token` (HTTP) - `__Secure-better-auth.session_token` (HTTPS with Secure prefix) - Redirects to login if no session - Allows public access to: - `/login` page - `/api/*` routes (API auth handled separately) - Static assets (`/_next`, `/favicon`, files with extensions) - **Important**: Middleware runs at Edge Runtime, so **cannot use Prisma** - only cookie check - Preserves callbackUrl for redirect after login **basePath Gotcha**: - `request.nextUrl.pathname` does NOT include basePath - Path comparison is done without `/studio` prefix - Redirect URLs must manually prepend basePath ### Better Auth Configuration **File**: `lib/auth.ts` ```typescript export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: 'mysql' }), emailAndPassword: { enabled: true }, basePath: '/api/auth', trustedOrigins: [process.env.BETTER_AUTH_URL], secret: process.env.BETTER_AUTH_SECRET, session: { expiresIn: 60 * 60 * 24 * 7, // 7 days updateAge: 60 * 60 * 24, // Update token every 1 day of activity }, }) ``` **Key Points**: - Uses Prisma adapter for MySQL - basePath: `/api/auth` (NOT `/studio/api/auth`) - BETTER_AUTH_URL must be origin only (NO path component) - Secret must be 32+ characters - Sessions auto-refresh if 1+ day of activity ### Client Auth Hooks **File**: `lib/auth-client.ts` ```typescript export const { signIn, signOut, useSession } = authClient ``` Provides: - `useSession()` hook - Get current session data - `signIn()` - Login function - `signOut()` - Logout function - Automatically uses basePath + appUrl to construct auth endpoint URL --- ## 7. LIBRARY UTILITIES ### `lib/api.ts` - API Fetch Helper ```typescript export function apiFetch(input: string, init?: RequestInit): Promise ``` - Prepends `basePath` to API routes - Example: `apiFetch('/api/levels')` → `/studio/api/levels` - Used throughout all client components ### `lib/prisma.ts` - Prisma Singleton ```typescript const prisma = globalForPrisma.prisma ?? new PrismaClient() ``` - Prevents multiple Prisma instances in development (hot reload) - Exports single prisma instance for all API routes ### `lib/cos.ts` - Tencent COS Utilities **Functions**: - `getTempKey()` - Get temporary STS credentials - Duration: 30 minutes - Policy: Upload only to `mini_game/images/*` - `getBucketName()` - Format bucket name (appends APPID if needed) - `getBucketConfig()` - Get bucket and region for SDK ### `lib/utils.ts` - General Utilities ```typescript export function cn(...inputs: ClassValue[]) ``` - Tailwind CSS utility merger (clsx + tailwind-merge) - Used throughout components for className management --- ## 8. UI COMPONENTS & PATTERNS ### Component Structure All components use React patterns: - **Client Components**: `'use client'` directive - **Controlled Forms**: State-driven form handling - **TanStack Query**: For server state management - **Dialog-based Operations**: Create/Edit in modals ### Key Components #### Pages 1. **Levels Page** (`app/(dashboard)/levels/page.tsx`) - List all levels with drag-and-drop reordering - Add/Edit/Delete levels - Uses TanStack Query mutations 2. **Users Page** (`app/(dashboard)/users/page.tsx`) - Table view of portal users - Add/Edit/Delete users - Delete prevention (can't delete self) 3. **WeChat Users Page** (`app/(dashboard)/wx-users/page.tsx`) - Search and pagination - Click to view user details + progress history - Batch delete progress records #### UI Components - **Button**: shadcn/ui with variants (default, outline, ghost) - **Input/Textarea**: Form inputs - **Dialog**: Modal dialogs with header, content, footer - **Spinner**: Loading indicator - **Card**: Container component ### Image Upload Flow 1. User clicks upload in level dialog 2. Frontend calls `/api/cos/temp-key` to get credentials 3. Frontend uses Tencent COS SDK to upload directly to COS 4. COS returns image URL 5. URL stored in database ### Form Submission Pattern ```typescript // TanStack Query mutation const mutation = useMutation({ mutationFn: async (data) => { /* API call */ }, onSuccess: () => { queryClient.invalidateQueries(...) } }) // On form submit await mutation.mutateAsync(data) // Automatic refetch on success ``` --- ## 9. ENVIRONMENT VARIABLES ### Required Variables ``` # Database DATABASE_URL=mysql://user:password@host:port/database # Better Auth BETTER_AUTH_SECRET=32+ character random string BETTER_AUTH_URL=https://domain.com (origin only, NO path) # Public URLs (Client-side) NEXT_PUBLIC_APP_URL=https://domain.com (same as BETTER_AUTH_URL) NEXT_PUBLIC_BASE_PATH=/studio # Admin Account (Seed script) ADMIN_EMAIL=admin@example.com ADMIN_PASSWORD=secure_password # Tencent COS COS_SECRET_ID=AKID... COS_SECRET_KEY=... COS_BUCKET=lookai-1308511832 COS_REGION=ap-guangzhou COS_APPID=1308511832 ``` ### Important Notes - **BETTER_AUTH_URL**: Must NOT contain path (e.g., ❌ `https://domain.com/studio`, ✅ `https://domain.com`) - **NEXT_PUBLIC_BASE_PATH**: Used client-side for apiFetch - **COS_BUCKET**: Can include or exclude APPID (utility handles both) --- ## 10. DEPLOYMENT & CONFIGURATION ### Next.js Configuration (`next.config.js`) ```javascript { output: 'standalone', // Single binary output basePath: '/studio', // App served at /studio images: { remotePatterns: [ { protocol: 'https', hostname: '*.myqcloud.com' } // Allow COS images ] } } ``` ### Deployment Setup **Server**: `root@119.91.211.52` at `/root/apps/meme-studio` **Process Manager**: PM2 (`ecosystem.config.js`) **Deploy Script** (`./deploy.sh`): 1. Build locally: `next build` 2. rsync files (excludes node_modules, .next) 3. SSH to server, run: - `npm install --production` - `npx prisma generate` - Restart PM2 process **Key Gotchas**: - Prisma is devDependency but needs to be generated on server - `output: 'standalone'` bundles Next.js runtime - Deployment requires `DATABASE_URL` set on server - Cross-platform binary target: `rhel-openssl-3.0.x` for Linux server --- ## 11. EXISTING PATTERNS & CONVENTIONS ### API Route Pattern ```typescript // 1. Import required modules import { auth } from '@/lib/auth' import { prisma } from '@/lib/prisma' // 2. Check session const session = await auth.api.getSession({ headers: request.headers }) if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) // 3. Parse request const body = await request.json() const { searchParams } = new URL(request.url) // 4. Validate input if (!required_field) return NextResponse.json({ error: 'msg' }, { status: 400 }) // 5. Database operation const result = await prisma.model.operation() // 6. Return response return NextResponse.json(result, { status: 201 }) ``` ### Mutation Pattern (Client) ```typescript const mutation = useMutation({ mutationFn: async (data) => { const res = await apiFetch('/api/endpoint', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) if (!res.ok) { const error = await res.json() throw new Error(error.error || 'Failed') } return res.json() }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['levels'] }) }, }) mutation.mutate(data) ``` ### Type Definitions Pattern ```typescript // Database model export interface Level { id: string imageUrl: string // ... createdAt: Date updatedAt: Date } // Form data (typically a subset) export interface LevelFormData { imageUrl: string answer: string hint1?: string // ... (no id, dates for new records) } ``` --- ## 12. KEY GOTCHAS & IMPORTANT NOTES ### 1. basePath Handling - ✅ `request.nextUrl.pathname` excludes basePath (returns `/levels`, not `/studio/levels`) - ❌ Never manually prepend `/studio` in API routes - ✅ Client-side: use `apiFetch()` which prepends basePath automatically - ❌ `router.push()` auto-prepends basePath, don't add manually ### 2. Better Auth Configuration - **CRITICAL**: `BETTER_AUTH_URL` must be origin only, NO path component - Better Auth's `withPath()` silently ignores `basePath` if URL has path - Cookie naming: HTTPS adds `__Secure-` prefix - Middleware must check both cookie names ### 3. Session Validation - ✅ Middleware: Cookie check only (no Prisma in Edge Runtime) - ✅ API Routes: Full session validation with `auth.api.getSession()` - Middleware doesn't verify token validity, only presence ### 4. Database Relationships - **No explicit join tables** for many-to-many (not needed here) - One-to-many: WxUser → WxUserLevelProgress → Level - Cascade delete: Sessions/Accounts deleted when User deleted ### 5. Image Upload Flow - Frontend gets temp credentials from `/api/cos/temp-key` - Frontend uploads directly to COS (not through backend) - Backend only stores URL - URLs expire or become invalid if credentials expire ### 6. Authentication Flow - Login → Session created → Cookie set - Page navigation → Middleware checks cookie - Redirects to login if missing - All API calls check session in route handler ### 7. Search Patterns - WeChat users: `OR` condition on nickname OR openid contains - Case-sensitive by default (MySQL depends on collation) - Pagination: `skip = (page - 1) * limit` --- ## 13. ABSENCE OF SHARE/INVITE LOGIC **Current Status**: ❌ NO sharing/invite functionality exists **What's Missing**: - No share link/token model in schema - No invite API endpoint - No permissions/role system - All authenticated users have full admin access to: - Level management - User management - WeChat user data **Considerations for Implementation**: - Determine if shares/invites are for: - Sharing levels with mini-program users? - Inviting new admins to platform? - Sharing user progress data? - Would need new database model - Permission checks in API routes - Share link generation and validation - Expiration logic --- ## 14. RECOMMENDATIONS FOR EXPLORATION ### For Share/Invite Feature (if needed): 1. **Schema Design**: - ShareToken model with expiry, permissions - Or UserRole model for permission levels 2. **API Endpoints Needed**: - `POST /api/shares` - Generate share link - `GET /api/shares/[token]` - Validate share token - `DELETE /api/shares/[id]` - Revoke share 3. **Permission System**: - Add role field to User model - Permission checks in middleware or API routes 4. **Frontend Needed**: - Share dialog component - Permission management UI --- ## SUMMARY TABLE | Category | Item | Details | |----------|------|---------| | **Framework** | Next.js | 14.2.28, App Router, Standalone output | | **Auth** | Better Auth | Email/password, MySQL Prisma adapter | | **Database** | MySQL | Prisma ORM, 7 models | | **Storage** | Tencent COS | STS temp credentials, 30 min validity | | **UI** | shadcn/ui | Tailwind, React Query, @dnd-kit | | **Deployment** | PM2 | Server at 119.91.211.52, basePath=/studio | | **Auth Routes** | 5 endpoints | Levels, Users, WxUsers, COS, Auth | | **Protected Routes** | YES | Middleware + per-route checks | | **Sharing** | ❌ NONE | Not implemented | | **Permissions** | ❌ FLAT | All authenticated users = full admin | | **Search** | Implemented | WeChat users only (nickname, openid) |