27 KiB
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)
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
sortOrderis used for drag-and-drop reordering- Related to WxUserLevelProgress (one-to-many)
2. User (Admin/Portal Users)
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)
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)
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)
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)
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)
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_URLenv 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 registrationPOST /api/auth/sign-in- User loginPOST /api/auth/sign-out- User logoutGET /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:
[ { "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:
{ "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:
{ "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:
{ "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:
{ "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:
{ "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:
{ "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
selectto exclude sessionKey
GET /api/wx-users/[id]
- Auth: Required
- Purpose: Get single WeChat user with level progress history
- Params:
id- User UUID - Returns:
{ "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:
{ "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:
{ "credentials": { "tmpSecretId": "string", "tmpSecretKey": "string", "sessionToken": "string" }, "startTime": 1712345678, "expiredTime": 1712347478, "bucket": "lookai-1308511832", "region": "ap-guangzhou" }
Authentication Patterns
- Session Check Pattern:
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:
/loginpage/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.pathnamedoes NOT include basePath- Path comparison is done without
/studioprefix - Redirect URLs must manually prepend basePath
Better Auth Configuration
File: lib/auth.ts
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
export const { signIn, signOut, useSession } = authClient
Provides:
useSession()hook - Get current session datasignIn()- Login functionsignOut()- Logout function- Automatically uses basePath + appUrl to construct auth endpoint URL
7. LIBRARY UTILITIES
lib/api.ts - API Fetch Helper
export function apiFetch(input: string, init?: RequestInit): Promise<Response>
- Prepends
basePathto API routes - Example:
apiFetch('/api/levels')→/studio/api/levels - Used throughout all client components
lib/prisma.ts - Prisma Singleton
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
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
-
Levels Page (
app/(dashboard)/levels/page.tsx)- List all levels with drag-and-drop reordering
- Add/Edit/Delete levels
- Uses TanStack Query mutations
-
Users Page (
app/(dashboard)/users/page.tsx)- Table view of portal users
- Add/Edit/Delete users
- Delete prevention (can't delete self)
-
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
- User clicks upload in level dialog
- Frontend calls
/api/cos/temp-keyto get credentials - Frontend uses Tencent COS SDK to upload directly to COS
- COS returns image URL
- URL stored in database
Form Submission Pattern
// 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)
{
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):
- Build locally:
next build - rsync files (excludes node_modules, .next)
- SSH to server, run:
npm install --productionnpx 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_URLset on server - Cross-platform binary target:
rhel-openssl-3.0.xfor Linux server
11. EXISTING PATTERNS & CONVENTIONS
API Route Pattern
// 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)
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
// 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.pathnameexcludes basePath (returns/levels, not/studio/levels) - ❌ Never manually prepend
/studioin 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_URLmust be origin only, NO path component - Better Auth's
withPath()silently ignoresbasePathif 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:
ORcondition 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):
-
Schema Design:
- ShareToken model with expiry, permissions
- Or UserRole model for permission levels
-
API Endpoints Needed:
POST /api/shares- Generate share linkGET /api/shares/[token]- Validate share tokenDELETE /api/shares/[id]- Revoke share
-
Permission System:
- Add role field to User model
- Permission checks in middleware or API routes
-
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) |