872 lines
24 KiB
Markdown
872 lines
24 KiB
Markdown
# MemeStudio Server Project - Thorough Exploration Report
|
|
|
|
## 1. PROJECT OVERVIEW
|
|
|
|
**Project Name:** Meme Studio (Homophone Pun Game Operation Platform)
|
|
**Framework:** Next.js 14 (App Router)
|
|
**Type:** Backend/Admin Dashboard for a wordplay game
|
|
**Location:** `/Users/richard/Documents/code/xieyingeng/MemeStudio`
|
|
|
|
### Tech Stack
|
|
- **Framework**: Next.js 14 App Router
|
|
- **Runtime**: Node.js (Standalone output)
|
|
- **Language**: TypeScript
|
|
- **ORM**: Prisma v6.5.0
|
|
- **Database**: MySQL
|
|
- **Authentication**: Better Auth v1.2.7 (email/password with Prisma adapter)
|
|
- **Frontend UI**: shadcn/ui + Tailwind CSS
|
|
- **State Management**: TanStack Query v5.69.0
|
|
- **Drag & Drop**: @dnd-kit/sortable
|
|
- **File Upload**: Tencent COS (Cloud Object Storage)
|
|
- **Password Hashing**: bcryptjs v3.0.2
|
|
- **Process Manager**: PM2 (ecosystem.config.js)
|
|
|
|
---
|
|
|
|
## 2. PROJECT STRUCTURE
|
|
|
|
```
|
|
MemeStudio/
|
|
├── app/ # Next.js App Router
|
|
│ ├── (auth)/ # Auth routes (no sidebar)
|
|
│ │ ├── layout.tsx
|
|
│ │ └── login/page.tsx
|
|
│ ├── (dashboard)/ # Protected routes (with sidebar)
|
|
│ │ ├── layout.tsx
|
|
│ │ ├── levels/page.tsx # Game levels management
|
|
│ │ ├── users/page.tsx # Admin user management
|
|
│ │ └── wx-users/page.tsx # WeChat mini program users
|
|
│ ├── api/ # API routes
|
|
│ │ ├── auth/[...all]/ # Better Auth endpoints
|
|
│ │ ├── levels/ # Level CRUD
|
|
│ │ ├── users/ # User CRUD
|
|
│ │ ├── wx-users/ # WeChat user endpoints
|
|
│ │ ├── cos/temp-key/ # Tencent COS credentials
|
|
│ │ └── v1/wechat-game/ # (empty, future API)
|
|
│ ├── layout.tsx # Root layout
|
|
│ ├── page.tsx # Root page (redirects to /dashboard)
|
|
│ ├── providers.tsx # Client providers (Query, etc)
|
|
│ └── globals.css
|
|
├── lib/ # Core utilities
|
|
│ ├── auth.ts # Better Auth config
|
|
│ ├── auth-client.ts # Client-side auth hooks
|
|
│ ├── prisma.ts # Prisma client singleton
|
|
│ ├── api.ts # API fetch wrapper with basePath
|
|
│ ├── cos.ts # Tencent COS utilities
|
|
│ └── utils.ts # UI utilities (cn())
|
|
├── components/ # React components
|
|
│ ├── ui/ # shadcn/ui components (button, card, dialog, input, etc)
|
|
│ ├── layout/ # Header, sidebar
|
|
│ ├── levels/ # Level-related components
|
|
│ ├── users/ # User dialog
|
|
│ └── wx-users/ # WeChat user detail dialog
|
|
├── types/index.ts # TypeScript interfaces
|
|
├── middleware.ts # Next.js middleware (session check)
|
|
├── prisma/
|
|
│ ├── schema.prisma # Prisma schema
|
|
│ └── seed.ts # Admin user seeding
|
|
├── public/ # Static assets
|
|
├── next.config.js # Next.js config (basePath, COS remotePatterns)
|
|
├── tsconfig.json
|
|
├── tailwind.config.ts
|
|
├── package.json
|
|
├── ecosystem.config.js # PM2 config
|
|
└── deploy.sh # Deployment script
|
|
```
|
|
|
|
---
|
|
|
|
## 3. AUTHENTICATION & MIDDLEWARE
|
|
|
|
### Middleware Pattern (`middleware.ts`)
|
|
|
|
```typescript
|
|
// Non-blocking: API routes, static files, login page
|
|
// Blocking: All dashboard pages require session cookie
|
|
|
|
// Cookie detection:
|
|
- 'better-auth.session_token' (HTTP)
|
|
- '__Secure-better-auth.session_token' (HTTPS)
|
|
|
|
// Redirect pattern:
|
|
- Unauthenticated users → /login?callbackUrl=/original/path
|
|
- Note: basePath (/studio) is NOT included in pathname within middleware
|
|
```
|
|
|
|
### Auth Implementation (`lib/auth.ts`)
|
|
|
|
```typescript
|
|
betterAuth({
|
|
database: prismaAdapter(prisma),
|
|
emailAndPassword: { enabled: true },
|
|
basePath: '/api/auth', // CRITICAL: NO basePath prefix here
|
|
secret: process.env.BETTER_AUTH_SECRET,
|
|
session: {
|
|
expiresIn: 7 days,
|
|
updateAge: 1 day,
|
|
},
|
|
})
|
|
```
|
|
|
|
### Key Auth Files
|
|
- `lib/auth.ts` - Server-side Better Auth configuration
|
|
- `lib/auth-client.ts` - Client-side hooks: `useSession`, `signIn`, `signOut`
|
|
- `middleware.ts` - Edge Runtime middleware (cannot use Prisma, only cookie check)
|
|
- `app/api/auth/[...all]/route.ts` - Better Auth endpoints
|
|
|
|
---
|
|
|
|
## 4. DATABASE SCHEMA (Prisma)
|
|
|
|
### Models
|
|
|
|
#### 1. **User** (Admin/staff 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[]
|
|
}
|
|
```
|
|
|
|
#### 2. **Session** (Better Auth)
|
|
```prisma
|
|
model Session {
|
|
id String @id
|
|
expiresAt DateTime
|
|
token String @unique # Critical for session validation
|
|
ipAddress String?
|
|
userAgent String?
|
|
userId String
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
}
|
|
```
|
|
|
|
#### 3. **Account** (Provider credentials)
|
|
```prisma
|
|
model Account {
|
|
id String @id @default(uuid())
|
|
accountId String
|
|
providerId String # 'credential' for email/password
|
|
userId String
|
|
password String? # Only for 'credential' provider
|
|
accessToken String?
|
|
refreshToken String?
|
|
# ... expires, scope, tokens
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
}
|
|
```
|
|
|
|
#### 4. **Verification** (Email verification tokens)
|
|
```prisma
|
|
model Verification {
|
|
id String @id @default(uuid())
|
|
identifier String
|
|
value String
|
|
expiresAt DateTime
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
```
|
|
|
|
#### 5. **Level** (Game levels)
|
|
```prisma
|
|
model Level {
|
|
id String @id @default(uuid())
|
|
imageUrl String # COS URL
|
|
answer String # Correct answer (Chinese pun text)
|
|
hint1 String?
|
|
hint2 String?
|
|
hint3 String?
|
|
sortOrder Int @default(0) # Display order
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
userProgress WxUserLevelProgress[]
|
|
}
|
|
```
|
|
|
|
#### 6. **WxUser** (WeChat mini program players)
|
|
```prisma
|
|
model WxUser {
|
|
id String @id @default(uuid())
|
|
openid String @unique # WeChat OpenID
|
|
sessionKey String? # WeChat session key
|
|
nickname String?
|
|
avatarUrl String?
|
|
points Int @default(10)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
levelProgress WxUserLevelProgress[]
|
|
}
|
|
```
|
|
|
|
#### 7. **WxUserLevelProgress** (Game progress tracking)
|
|
```prisma
|
|
model WxUserLevelProgress {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
levelId String
|
|
completedAt DateTime @default(now()) # When level was completed
|
|
|
|
user WxUser @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
level Level @relation(fields: [levelId], references: [id])
|
|
}
|
|
```
|
|
|
|
### Database Configuration
|
|
- **Provider**: MySQL
|
|
- **Binary Targets**: `["native", "rhel-openssl-3.0.x"]` (macOS dev → Linux server compatibility)
|
|
- **Environment Variable**: `DATABASE_URL=mysql://...`
|
|
|
|
---
|
|
|
|
## 5. API ROUTES & ENDPOINTS
|
|
|
|
### 5.1 Authentication Endpoints
|
|
**Route:** `/api/auth/[...all]`
|
|
**Handler:** Better Auth (`toNextJsHandler(auth)`)
|
|
**Methods:** GET, POST
|
|
**Auth Required:** No (this is the auth endpoint itself)
|
|
|
|
Common endpoints:
|
|
- `POST /api/auth/sign-in/email` - Email sign in
|
|
- `POST /api/auth/sign-out` - Sign out
|
|
- `POST /api/auth/sign-up/email` - Create new account (might be disabled)
|
|
|
|
---
|
|
|
|
### 5.2 Levels API
|
|
**Route:** `/api/levels`
|
|
**Auth Required:** Yes (session check)
|
|
|
|
#### GET - Fetch all levels
|
|
```typescript
|
|
GET /api/levels
|
|
Response: Level[]
|
|
Sorting: By sortOrder (ASC)
|
|
```
|
|
|
|
#### POST - Create level
|
|
```typescript
|
|
POST /api/levels
|
|
Body: {
|
|
imageUrl: string (required),
|
|
answer: string (required),
|
|
hint1?: string,
|
|
hint2?: string,
|
|
hint3?: string
|
|
}
|
|
Response: Level (with auto-generated UUID, sortOrder = max+1)
|
|
```
|
|
|
|
#### PUT - Update level
|
|
```typescript
|
|
PUT /api/levels
|
|
Body: {
|
|
id: string (required),
|
|
imageUrl?: string,
|
|
answer?: string,
|
|
hint1?: string,
|
|
hint2?: string,
|
|
hint3?: string
|
|
}
|
|
Response: Updated Level
|
|
```
|
|
|
|
#### DELETE - Delete level
|
|
```typescript
|
|
DELETE /api/levels?id=<levelId>
|
|
Response: { success: true }
|
|
```
|
|
|
|
---
|
|
|
|
### 5.3 Levels Reorder API
|
|
**Route:** `/api/levels/reorder`
|
|
**Auth Required:** Yes
|
|
|
|
#### PUT - Batch update sort order
|
|
```typescript
|
|
PUT /api/levels/reorder
|
|
Body: {
|
|
orders: Array<{ id: string, sortOrder: number }>
|
|
}
|
|
Response: { success: true }
|
|
Implementation: Prisma $transaction (atomic operation)
|
|
```
|
|
|
|
**Use Case:** Drag & drop reordering in UI
|
|
|
|
---
|
|
|
|
### 5.4 Users API (Admin)
|
|
**Route:** `/api/users`
|
|
**Auth Required:** Yes
|
|
**Purpose:** Manage admin/staff accounts
|
|
|
|
#### GET - Fetch all users
|
|
```typescript
|
|
GET /api/users
|
|
Response: Array<{
|
|
id, email, emailVerified, name, image, createdAt, updatedAt
|
|
}>
|
|
Sorting: By createdAt DESC
|
|
```
|
|
|
|
#### POST - Create new admin user
|
|
```typescript
|
|
POST /api/users
|
|
Body: {
|
|
email: string (required),
|
|
password: string (required),
|
|
name?: string
|
|
}
|
|
Response: User object
|
|
- Auto-generates UUID for user ID
|
|
- Hashes password with better-auth/crypto
|
|
- Creates User + Account records in transaction
|
|
- Error: "该邮箱已被注册" if email exists
|
|
```
|
|
|
|
#### PUT - Update user
|
|
```typescript
|
|
PUT /api/users
|
|
Body: {
|
|
id: string (required),
|
|
email?: string,
|
|
password?: string,
|
|
name?: string
|
|
}
|
|
Response: Updated User
|
|
- Can update email (checks uniqueness vs other users)
|
|
- Can update password (hashes with better-auth)
|
|
- Transaction: Updates User + Account records
|
|
```
|
|
|
|
#### DELETE - Delete user
|
|
```typescript
|
|
DELETE /api/users?id=<userId>
|
|
Response: { success: true }
|
|
- Prevents self-deletion: "不能删除自己的账户"
|
|
- Cascading delete via Prisma relations
|
|
```
|
|
|
|
**Password Hashing:** Always uses `hashPassword` from `better-auth/crypto` for consistency
|
|
|
|
---
|
|
|
|
### 5.5 WeChat Users API
|
|
**Route:** `/api/wx-users`
|
|
**Auth Required:** Yes
|
|
**Purpose:** Manage WeChat mini program player accounts
|
|
|
|
#### GET - List WeChat users (paginated + search)
|
|
```typescript
|
|
GET /api/wx-users?search=<text>&page=<1>&limit=<20>
|
|
Response: {
|
|
users: Array<WxUser>,
|
|
meta: { total, page, limit, totalPages }
|
|
}
|
|
Filtering: Search by nickname OR openid (contains search)
|
|
Sorting: By createdAt DESC
|
|
Dynamic Rendering: export const dynamic = 'force-dynamic'
|
|
```
|
|
|
|
---
|
|
|
|
### 5.6 WeChat User Detail
|
|
**Route:** `/api/wx-users/[id]`
|
|
**Auth Required:** Yes
|
|
**Method:** GET
|
|
|
|
```typescript
|
|
GET /api/wx-users/<userId>
|
|
Response: WxUser with nested levelProgress array
|
|
Include:
|
|
- All WxUser fields
|
|
- levelProgress: Array of {
|
|
id,
|
|
userId,
|
|
levelId,
|
|
completedAt,
|
|
level: { id, answer }
|
|
}
|
|
Sorting: levelProgress by completedAt DESC
|
|
```
|
|
|
|
---
|
|
|
|
### 5.7 WeChat User Level Progress
|
|
**Route:** `/api/wx-users/level-progress`
|
|
**Auth Required:** Yes
|
|
**Method:** DELETE
|
|
|
|
```typescript
|
|
DELETE /api/wx-users/level-progress
|
|
Body: {
|
|
ids: Array<string> # Array of progress record IDs
|
|
}
|
|
Response: { deleted: number }
|
|
Validation: ids must be non-empty array of strings
|
|
```
|
|
|
|
**Use Case:** Batch delete player progress (e.g., reset levels)
|
|
|
|
---
|
|
|
|
### 5.8 Tencent COS Credentials
|
|
**Route:** `/api/cos/temp-key`
|
|
**Auth Required:** Yes
|
|
**Method:** GET
|
|
|
|
```typescript
|
|
GET /api/cos/temp-key
|
|
Response: {
|
|
credentials: {
|
|
tmpSecretId: string,
|
|
tmpSecretKey: string,
|
|
sessionToken: string
|
|
},
|
|
startTime: number,
|
|
expiredTime: number,
|
|
bucket: string,
|
|
region: string
|
|
}
|
|
```
|
|
|
|
**Implementation Details:**
|
|
- Uses `qcloud-cos-sts` library
|
|
- Policy allows: `cos:PutObject`, `cos:PostObject`
|
|
- Limited to: `mini_game/images/*` directory
|
|
- Duration: 30 minutes (1800 seconds)
|
|
- Returns bucket config for frontend upload
|
|
|
|
---
|
|
|
|
### 5.9 Empty/Stub Routes
|
|
- `/api/v1/wechat-game/` - Directory structure exists but no handlers
|
|
|
|
---
|
|
|
|
## 6. LIB UTILITIES
|
|
|
|
### 6.1 `lib/auth.ts` - Better Auth Configuration
|
|
```typescript
|
|
export const auth = betterAuth({
|
|
database: prismaAdapter(prisma),
|
|
emailAndPassword: { enabled: true },
|
|
basePath: '/api/auth', // CRITICAL GOTCHA
|
|
trustedOrigins: [process.env.BETTER_AUTH_URL],
|
|
secret: process.env.BETTER_AUTH_SECRET,
|
|
session: { expiresIn: 7 days, updateAge: 1 day }
|
|
})
|
|
export type Auth = typeof auth
|
|
```
|
|
|
|
**Critical Gotchas:**
|
|
1. `basePath` must be `/api/auth` (NOT `/studio/api/auth`)
|
|
2. `BETTER_AUTH_URL` must be origin-only (NOT include path)
|
|
3. Better Auth's `withPath()` silently ignores basePath if URL has path component
|
|
|
|
---
|
|
|
|
### 6.2 `lib/auth-client.ts` - Client Auth Hooks
|
|
```typescript
|
|
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '/studio'
|
|
const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3001'
|
|
|
|
export const authClient = createAuthClient({
|
|
baseURL: `${appUrl}${basePath}/api/auth`
|
|
})
|
|
|
|
export const { signIn, signOut, useSession } = authClient
|
|
```
|
|
|
|
**Usage:**
|
|
- `useSession()` - Hook to get current session
|
|
- `signIn(email, password)` - Email/password login
|
|
- `signOut()` - Logout
|
|
|
|
---
|
|
|
|
### 6.3 `lib/prisma.ts` - Prisma Singleton
|
|
```typescript
|
|
const globalForPrisma = globalThis as unknown as {
|
|
prisma: PrismaClient | undefined
|
|
}
|
|
|
|
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
|
|
|
|
if (process.env.NODE_ENV !== 'production')
|
|
globalForPrisma.prisma = prisma
|
|
```
|
|
|
|
**Purpose:** Prevent multiple Prisma client instances in dev mode
|
|
|
|
---
|
|
|
|
### 6.4 `lib/api.ts` - API Fetch Helper
|
|
```typescript
|
|
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ''
|
|
|
|
export function apiFetch(input: string, init?: RequestInit): Promise<Response> {
|
|
const url = input.startsWith('/') ? `${basePath}${input}` : input
|
|
return fetch(url, init)
|
|
}
|
|
```
|
|
|
|
**Purpose:** Wrapper that auto-prepends basePath to relative URLs (client-side)
|
|
|
|
---
|
|
|
|
### 6.5 `lib/cos.ts` - Tencent COS Utilities
|
|
```typescript
|
|
interface TempKeyResult {
|
|
credentials: {
|
|
tmpSecretId: string
|
|
tmpSecretKey: string
|
|
sessionToken: string
|
|
}
|
|
startTime: number
|
|
expiredTime: number
|
|
}
|
|
|
|
export function getBucketName(): string
|
|
// Returns "bucket-appid" format
|
|
|
|
export async function getTempKey(): Promise<TempKeyResult>
|
|
// STS.getCredential with 30-min duration
|
|
// Policy: PUT/POST to mini_game/images/*
|
|
|
|
export function getBucketConfig()
|
|
// Returns { bucket, region }
|
|
```
|
|
|
|
**Environment Variables:**
|
|
- `COS_SECRET_ID`
|
|
- `COS_SECRET_KEY`
|
|
- `COS_BUCKET`
|
|
- `COS_REGION` (default: ap-guangzhou)
|
|
- `COS_APPID`
|
|
|
|
---
|
|
|
|
### 6.6 `lib/utils.ts` - UI Utilities
|
|
```typescript
|
|
export function cn(...inputs: ClassValue[])
|
|
// Combines clsx + tailwind-merge for class deduplication
|
|
```
|
|
|
|
---
|
|
|
|
## 7. ENVIRONMENT VARIABLES
|
|
|
|
**Required Variables:**
|
|
```
|
|
DATABASE_URL=mysql://user:pass@host:port/dbname
|
|
BETTER_AUTH_SECRET=<32+ chars, openssl rand -base64 32>
|
|
BETTER_AUTH_URL=http://localhost:3001 # Origin only, NO path
|
|
NEXT_PUBLIC_APP_URL=http://localhost:3001
|
|
NEXT_PUBLIC_BASE_PATH=/studio
|
|
ADMIN_EMAIL=admin@example.com
|
|
ADMIN_PASSWORD=password123
|
|
COS_SECRET_ID=<tencent cloud secret>
|
|
COS_SECRET_KEY=<tencent cloud secret>
|
|
COS_BUCKET=bucket-name
|
|
COS_REGION=ap-guangzhou
|
|
COS_APPID=<tencent app id>
|
|
```
|
|
|
|
**Production Considerations:**
|
|
- `.env.production` exists alongside `.env`
|
|
- `BETTER_AUTH_SECRET` should be 32+ characters
|
|
- HTTPS deployment adds `__Secure-` cookie prefix
|
|
- Database should use SSL connection
|
|
|
|
---
|
|
|
|
## 8. DEPLOYMENT ARCHITECTURE
|
|
|
|
### Standalone Output
|
|
```javascript
|
|
// next.config.js
|
|
module.exports = {
|
|
output: 'standalone', # Self-contained binary
|
|
basePath: '/studio', # Reverse proxy path
|
|
images: {
|
|
remotePatterns: [{
|
|
protocol: 'https',
|
|
hostname: '*.myqcloud.com' # Tencent COS pattern
|
|
}]
|
|
}
|
|
}
|
|
```
|
|
|
|
### Deployment Flow (deploy.sh)
|
|
1. Build locally: `next build` (creates .next/standalone)
|
|
2. rsync files to server (excluding node_modules)
|
|
3. SSH to server: `npm install --production`
|
|
4. Generate Prisma: `npx prisma generate`
|
|
5. PM2 restart: `pm2 restart ecosystem.config.js`
|
|
|
|
### Server Details
|
|
- **Host:** root@119.91.211.52
|
|
- **Path:** /root/apps/meme-studio
|
|
- **Process Manager:** PM2
|
|
- **Config:** ecosystem.config.js
|
|
|
|
### PM2 Configuration
|
|
```javascript
|
|
// ecosystem.config.js (typical setup)
|
|
module.exports = {
|
|
apps: [{
|
|
name: 'meme-studio',
|
|
script: './.next/standalone/server.js',
|
|
instances: 1,
|
|
exec_mode: 'cluster'
|
|
}]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 9. CRITICAL GOTCHAS & IMPORTANT NOTES
|
|
|
|
### 9.1 basePath Handling
|
|
- ✅ `next.config.js` sets `basePath: '/studio'`
|
|
- ✅ All pages/API routes served under `/studio/...`
|
|
- ❌ **NEVER** include `/studio` in `BETTER_AUTH_URL` or hardcoded paths
|
|
- ⚠️ `request.nextUrl.pathname` in middleware EXCLUDES basePath (shows `/levels` not `/studio/levels`)
|
|
- ⚠️ Next.js strips basePath from `request.url` in route handlers
|
|
|
|
### 9.2 Authentication Patterns
|
|
- ✅ Session validation: `await auth.api.getSession({ headers: request.headers })`
|
|
- ✅ Session check in middleware uses cookies (Edge Runtime compatible)
|
|
- ⚠️ Password hashing: ALWAYS use `hashPassword` from `better-auth/crypto`
|
|
- ⚠️ Session token field has unique constraint in database
|
|
|
|
### 9.3 Database & ORM
|
|
- ✅ Prisma adapter with MySQL provider
|
|
- ✅ Binary targets: `["native", "rhel-openssl-3.0.x"]` for cross-platform deployment
|
|
- ⚠️ Middleware cannot use Prisma (Edge Runtime incompatible)
|
|
- ⚠️ Transaction usage for consistency: `prisma.$transaction([...])`
|
|
|
|
### 9.4 File Upload (COS)
|
|
- ✅ Frontend gets temporary credentials via `/api/cos/temp-key`
|
|
- ✅ Browser uploads directly to COS (S3-compatible)
|
|
- ✅ Limited to `mini_game/images/*` directory by policy
|
|
- ⚠️ Policy duration: 30 minutes (1800 seconds)
|
|
|
|
### 9.5 Git & Development
|
|
- ⚠️ **Commit messages must be written in Chinese**
|
|
- ✅ ESLint configured: `npm run lint`
|
|
- ✅ Dev server runs on port 3001: `npm run dev`
|
|
- ✅ Database seeding: `npm run db:seed` (creates/updates admin user)
|
|
|
|
---
|
|
|
|
## 10. SHARE/INVITE LOGIC
|
|
|
|
**Current Status:** ❌ **NO existing share or invite functionality**
|
|
|
|
No database models, API endpoints, or UI components for:
|
|
- Sharing levels with other users
|
|
- Generating shareable links/tokens
|
|
- Inviting users to teams or projects
|
|
- Publishing/unpublishing levels
|
|
- Level access control
|
|
|
|
**All levels are:**
|
|
- Managed by admin users
|
|
- Accessible to all authenticated admins
|
|
- Automatically available in the WeChat mini program
|
|
|
|
---
|
|
|
|
## 11. KEY PATTERNS USED
|
|
|
|
### 11.1 API Route Pattern
|
|
All protected API routes follow:
|
|
```typescript
|
|
import { auth } from '@/lib/auth'
|
|
|
|
export async function GET(request: NextRequest) {
|
|
const session = await auth.api.getSession({ headers: request.headers })
|
|
if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
|
|
// ... handler logic
|
|
}
|
|
```
|
|
|
|
### 11.2 Error Handling
|
|
- 400: Bad request (validation errors)
|
|
- 401: Unauthorized (no session)
|
|
- 404: Not found (resource doesn't exist)
|
|
- 500: Server error (logged to console)
|
|
|
|
### 11.3 Response Format
|
|
- Success: `NextResponse.json(data)`
|
|
- Error: `NextResponse.json({ error: string }, { status: number })`
|
|
- Batch operations: `{ success: true }` or `{ deleted: count }`
|
|
|
|
### 11.4 Transaction Pattern
|
|
```typescript
|
|
await prisma.$transaction(async (tx) => {
|
|
const user = await tx.user.create({ data: {...} })
|
|
await tx.account.create({ data: {...} })
|
|
return user
|
|
})
|
|
```
|
|
|
|
### 11.5 Pagination Pattern
|
|
```typescript
|
|
const skip = (page - 1) * limit
|
|
const [data, total] = await Promise.all([
|
|
prisma.model.findMany({ skip, take: limit }),
|
|
prisma.model.count()
|
|
])
|
|
return { data, meta: { total, page, limit, totalPages: Math.ceil(total/limit) } }
|
|
```
|
|
|
|
---
|
|
|
|
## 12. FILE STRUCTURE SUMMARY
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `middleware.ts` | Session validation, redirect to login |
|
|
| `next.config.js` | basePath, COS image patterns, standalone output |
|
|
| `lib/auth.ts` | Better Auth server configuration |
|
|
| `lib/auth-client.ts` | Client-side auth hooks |
|
|
| `lib/prisma.ts` | Prisma singleton |
|
|
| `lib/api.ts` | Fetch wrapper with basePath |
|
|
| `lib/cos.ts` | Tencent COS STS credentials |
|
|
| `prisma/schema.prisma` | Database schema (7 models) |
|
|
| `prisma/seed.ts` | Admin user seeding |
|
|
| `app/api/auth/[...all]/route.ts` | Better Auth endpoints |
|
|
| `app/api/levels/route.ts` | Level CRUD (GET, POST, PUT, DELETE) |
|
|
| `app/api/levels/reorder/route.ts` | Batch reorder (PUT) |
|
|
| `app/api/users/route.ts` | Admin user CRUD (GET, POST, PUT, DELETE) |
|
|
| `app/api/wx-users/route.ts` | List WeChat users (GET, paginated) |
|
|
| `app/api/wx-users/[id]/route.ts` | Get user + progress (GET) |
|
|
| `app/api/wx-users/level-progress/route.ts` | Delete progress (DELETE) |
|
|
| `app/api/cos/temp-key/route.ts` | Get COS credentials (GET) |
|
|
| `app/(dashboard)/levels/page.tsx` | Levels management UI |
|
|
| `app/(dashboard)/users/page.tsx` | Admin users management UI |
|
|
| `app/(dashboard)/wx-users/page.tsx` | WeChat users viewer UI |
|
|
| `app/(auth)/login/page.tsx` | Login page |
|
|
| `types/index.ts` | TypeScript interfaces (Level, User, WxUser, etc) |
|
|
|
|
---
|
|
|
|
## 13. COMPONENT STRUCTURE
|
|
|
|
**UI Components (shadcn/ui):**
|
|
- `button.tsx` - Button component
|
|
- `card.tsx` - Card container
|
|
- `dialog.tsx` - Modal dialog
|
|
- `input.tsx` - Text input
|
|
- `label.tsx` - Form label
|
|
- `textarea.tsx` - Textarea input
|
|
- `spinner.tsx` - Loading spinner
|
|
|
|
**Layout Components:**
|
|
- `header.tsx` - Top navigation header
|
|
- `sidebar.tsx` - Left sidebar with navigation
|
|
|
|
**Feature Components:**
|
|
- `levels/level-dialog.tsx` - Create/edit level modal
|
|
- `levels/level-list.tsx` - Drag-and-drop sortable list
|
|
- `levels/level-card.tsx` - Individual level card
|
|
- `levels/image-uploader.tsx` - COS image upload
|
|
- `users/user-dialog.tsx` - Create/edit admin user modal
|
|
- `wx-users/wx-user-detail-dialog.tsx` - View WeChat user progress
|
|
|
|
---
|
|
|
|
## 14. DATA FLOW EXAMPLES
|
|
|
|
### Example 1: Create Level
|
|
1. Admin clicks "Add Level" button
|
|
2. UI opens `level-dialog.tsx` modal
|
|
3. Admin fills form (imageUrl, answer, hints)
|
|
4. `image-uploader.tsx` gets temp COS credentials from `/api/cos/temp-key`
|
|
5. Browser uploads image directly to COS
|
|
6. Admin submits form
|
|
7. API calls `POST /api/levels` with imageUrl, answer, hints
|
|
8. Backend validates session, creates Level record
|
|
9. sortOrder auto-calculated (max+1)
|
|
10. Level appears in list (sorted by sortOrder)
|
|
|
|
### Example 2: Reorder Levels
|
|
1. Admin drags level up/down in `level-list.tsx`
|
|
2. @dnd-kit/sortable updates UI state
|
|
3. Admin confirms or auto-save triggers
|
|
4. API calls `PUT /api/levels/reorder` with reordered IDs
|
|
5. Backend updates sortOrder for all levels in transaction
|
|
6. List re-renders in new order
|
|
|
|
### Example 3: View WeChat Player
|
|
1. Admin opens "WeChat Users" dashboard page
|
|
2. `wx-users/page.tsx` calls `GET /api/wx-users?page=1&limit=20`
|
|
3. Backend queries paginated WxUser list
|
|
4. Admin clicks on player name
|
|
5. Opens `wx-user-detail-dialog.tsx`
|
|
6. Component calls `GET /api/wx-users/<id>`
|
|
7. Backend returns WxUser + nested levelProgress array
|
|
8. Dialog displays player info and completed levels
|
|
|
|
---
|
|
|
|
## 15. SECURITY CONSIDERATIONS
|
|
|
|
### Implemented
|
|
- ✅ Session-based authentication (Better Auth)
|
|
- ✅ Password hashing (bcryptjs via Better Auth)
|
|
- ✅ CSRF protection (Better Auth built-in)
|
|
- ✅ Middleware session validation
|
|
- ✅ COS temporary credentials (limited scope, 30-min expiry)
|
|
- ✅ SQL injection protection (Prisma ORM)
|
|
- ✅ Email uniqueness constraints
|
|
|
|
### Not Implemented
|
|
- ❌ Role-based access control (all authenticated users have admin access)
|
|
- ❌ Audit logging
|
|
- ❌ Rate limiting
|
|
- ❌ Input validation/sanitization (rely on Prisma types)
|
|
- ❌ API key authentication (only cookie-based)
|
|
- ❌ CORS configuration (not needed for same-origin)
|
|
|
|
---
|
|
|
|
## SUMMARY
|
|
|
|
MemeStudio is a **well-structured Next.js admin dashboard** for managing a WeChat mini program game. It has:
|
|
|
|
- **Clean Auth**: Better Auth with Prisma adapter, session-based
|
|
- **Full CRUD**: Levels, admin users, WeChat player tracking
|
|
- **Cloud Integration**: Tencent COS for image uploads
|
|
- **Scalable ORM**: Prisma with MySQL
|
|
- **No Sharing Logic**: All data is globally accessible to admins
|
|
|
|
**For Adding Share/Invite Feature:**
|
|
- Add `SharedLevel` or `LevelAccess` model (permissions)
|
|
- Add `InviteToken` model (temporary join links)
|
|
- Add `/api/invites` endpoints (generate, accept, list)
|
|
- Add role/permission fields to User model
|
|
- Add UI for managing shares/invites
|
|
|