945 lines
27 KiB
Markdown
945 lines
27 KiB
Markdown
# 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<Response>
|
|
```
|
|
- 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) |
|
|
|