recovery-coach-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRecovery Coach Development Patterns
Recovery Coach 开发模式
This skill helps you follow the established patterns and conventions in the Recovery Coach codebase.
本技能可帮助你遵循Recovery Coach代码库中已确立的模式与规范。
When to Use
适用场景
✅ USE this skill for:
- Writing new components, pages, or API routes in Recovery Coach
- Following established code organization patterns
- Implementing database queries with Drizzle ORM
- Understanding project architecture and conventions
- Styling components to match the design system
❌ DO NOT use for:
- Crisis intervention implementation → use
crisis-response-protocol - General Next.js questions → use Next.js docs
- AI/LLM integration patterns → use
modern-drug-rehab-computer - Content moderation → use
recovery-community-moderator
✅ 适用本技能的场景:
- 在Recovery Coach中编写新组件、页面或API路由
- 遵循已确立的代码组织模式
- 使用Drizzle ORM实现数据库查询
- 理解项目架构与规范
- 按照设计系统为组件设置样式
❌ 不适用本技能的场景:
- 危机干预实现 → 使用
crisis-response-protocol - 通用Next.js问题 → 参考Next.js官方文档
- AI/LLM集成模式 → 使用
modern-drug-rehab-computer - 内容审核 → 使用
recovery-community-moderator
Project Structure
项目结构
src/
├── app/ # Next.js App Router
│ ├── api/ # API routes (REST endpoints)
│ │ ├── auth/ # Authentication endpoints
│ │ ├── check-in/ # Daily check-in endpoints
│ │ ├── chat/ # AI coaching endpoints
│ │ └── admin/ # Admin-only endpoints
│ ├── admin/ # Admin dashboard page
│ ├── settings/ # User settings page
│ └── page.tsx # Home page
├── components/ # React components
│ ├── ui/ # Base UI components (Button, Card, etc.)
│ └── *.tsx # Feature components
├── lib/ # Core business logic
│ ├── ai/ # Anthropic integration
│ ├── hipaa/ # HIPAA compliance utilities
│ ├── auth.ts # Authentication
│ ├── db.ts # Database connection
│ └── rate-limit.ts # Rate limiting
├── db/ # Database schema (Drizzle ORM)
│ ├── schema.ts # Table definitions
│ └── secure-db.ts # RLS-enforced queries
└── test/ # Test utilities
features/ # Feature manifests (YAML)
docs/ # Documentation
scripts/ # Build and utility scriptssrc/
├── app/ # Next.js App Router
│ ├── api/ # API路由(REST端点)
│ │ ├── auth/ # 认证端点
│ │ ├── check-in/ # 每日签到端点
│ │ ├── chat/ # AI辅导端点
│ │ └── admin/ # 管理员专属端点
│ ├── admin/ # 管理员仪表盘页面
│ ├── settings/ # 用户设置页面
│ └── page.tsx # 首页
├── components/ # React组件
│ ├── ui/ # 基础UI组件(Button、Card等)
│ └── *.tsx # 业务特性组件
├── lib/ # 核心业务逻辑
│ ├── ai/ # Anthropic集成
│ ├── hipaa/ # HIPAA合规工具
│ ├── auth.ts # 认证模块
│ ├── db.ts # 数据库连接
│ └── rate-limit.ts # 限流模块
├── db/ # 数据库 schema(Drizzle ORM)
│ ├── schema.ts # 表定义
│ └── secure-db.ts # 启用RLS的查询
└── test/ # 测试工具
features/ # 业务特性清单(YAML格式)
docs/ # 文档
scripts/ # 构建与工具脚本API Route Pattern
API路由模式
typescript
// src/app/api/[feature]/route.ts
import { NextResponse } from 'next/server';
import { getSession } from '@/lib/auth';
import { createRateLimiter } from '@/lib/rate-limit';
import { logPHIAccess } from '@/lib/hipaa/audit';
import { z } from 'zod';
// 1. Define rate limiter
const rateLimiter = createRateLimiter({
windowMs: 60000,
maxRequests: 30,
keyPrefix: 'api:feature'
});
// 2. Define input schema
const RequestSchema = z.object({
field: z.string().min(1).max(1000),
});
export async function POST(request: Request) {
// 3. Check authentication
const session = await getSession();
if (!session) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
// 4. Apply rate limiting
const rateLimitResult = await rateLimiter.check(session.userId);
if (!rateLimitResult.allowed) {
return NextResponse.json(
{ error: 'Rate limit exceeded' },
{ status: 429, headers: rateLimitResult.headers }
);
}
// 5. Parse and validate input
const body = await request.json();
const parsed = RequestSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json(
{ error: 'Invalid input', details: parsed.error.issues },
{ status: 400 }
);
}
// 6. Perform operation
const result = await performOperation(session.userId, parsed.data);
// 7. Audit log (if PHI)
await logPHIAccess(session.userId, 'feature', result.id, 'CREATE');
// 8. Return response
return NextResponse.json(result);
}typescript
// src/app/api/[feature]/route.ts
import { NextResponse } from 'next/server';
import { getSession } from '@/lib/auth';
import { createRateLimiter } from '@/lib/rate-limit';
import { logPHIAccess } from '@/lib/hipaa/audit';
import { z } from 'zod';
// 1. 定义限流规则
const rateLimiter = createRateLimiter({
windowMs: 60000,
maxRequests: 30,
keyPrefix: 'api:feature'
});
// 2. 定义输入 schema
const RequestSchema = z.object({
field: z.string().min(1).max(1000),
});
export async function POST(request: Request) {
// 3. 检查认证状态
const session = await getSession();
if (!session) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
// 4. 应用限流
const rateLimitResult = await rateLimiter.check(session.userId);
if (!rateLimitResult.allowed) {
return NextResponse.json(
{ error: 'Rate limit exceeded' },
{ status: 429, headers: rateLimitResult.headers }
);
}
// 5. 解析并验证输入
const body = await request.json();
const parsed = RequestSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json(
{ error: 'Invalid input', details: parsed.error.issues },
{ status: 400 }
);
}
// 6. 执行操作
const result = await performOperation(session.userId, parsed.data);
// 7. 审计日志(若涉及PHI)
await logPHIAccess(session.userId, 'feature', result.id, 'CREATE');
// 8. 返回响应
return NextResponse.json(result);
}React Component Pattern
React组件模式
typescript
// src/components/FeatureComponent.tsx
'use client';
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/Button';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
interface FeatureProps {
id: string;
initialData?: FeatureData;
onComplete?: (result: Result) => void;
}
export function FeatureComponent({
id,
initialData,
onComplete
}: FeatureProps) {
const [data, setData] = useState<FeatureData | null>(initialData ?? null);
const [loading, setLoading] = useState(!initialData);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!initialData) {
fetchData();
}
}, [id]);
async function fetchData() {
try {
setLoading(true);
const res = await fetch(`/api/feature/${id}`);
if (!res.ok) throw new Error('Failed to fetch');
const data = await res.json();
setData(data);
} catch (e) {
setError(e instanceof Error ? e.message : 'Unknown error');
} finally {
setLoading(false);
}
}
if (loading) {
return <div className="animate-pulse">Loading...</div>;
}
if (error) {
return (
<Card className="border-destructive">
<CardContent>Error: {error}</CardContent>
</Card>
);
}
return (
<Card>
<CardHeader>Feature Title</CardHeader>
<CardContent>
{/* Content */}
</CardContent>
</Card>
);
}typescript
// src/components/FeatureComponent.tsx
'use client';
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/Button';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
interface FeatureProps {
id: string;
initialData?: FeatureData;
onComplete?: (result: Result) => void;
}
export function FeatureComponent({
id,
initialData,
onComplete
}: FeatureProps) {
const [data, setData] = useState<FeatureData | null>(initialData ?? null);
const [loading, setLoading] = useState(!initialData);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!initialData) {
fetchData();
}
}, [id]);
async function fetchData() {
try {
setLoading(true);
const res = await fetch(`/api/feature/${id}`);
if (!res.ok) throw new Error('Failed to fetch');
const data = await res.json();
setData(data);
} catch (e) {
setError(e instanceof Error ? e.message : 'Unknown error');
} finally {
setLoading(false);
}
}
if (loading) {
return <div className="animate-pulse">Loading...</div>;
}
if (error) {
return (
<Card className="border-destructive">
<CardContent>Error: {error}</CardContent>
</Card>
);
}
return (
<Card>
<CardHeader>Feature Title</CardHeader>
<CardContent>
{/* 内容 */}
</CardContent>
</Card>
);
}Database Query Pattern
数据库查询模式
typescript
// Use secure-db for user data (RLS enforced)
import { db, users, checkIns } from '@/db/secure-db';
import { eq, desc } from 'drizzle-orm';
// Get user's own data (RLS automatically filters)
async function getUserCheckIns(userId: string) {
return db
.select()
.from(checkIns)
.where(eq(checkIns.userId, userId))
.orderBy(desc(checkIns.createdAt))
.limit(30);
}
// For admin queries, use requireAdmin
import { requireAdmin } from '@/db/secure-db';
async function getAdminStats() {
const admin = await requireAdmin();
if (!admin) throw new Error('Admin required');
// Now can query across all users
return db.select({ count: count() }).from(users);
}typescript
// 使用 secure-db 处理用户数据(已启用RLS)
import { db, users, checkIns } from '@/db/secure-db';
import { eq, desc } from 'drizzle-orm';
// 获取用户自身数据(RLS自动过滤)
async function getUserCheckIns(userId: string) {
return db
.select()
.from(checkIns)
.where(eq(checkIns.userId, userId))
.orderBy(desc(checkIns.createdAt))
.limit(30);
}
// 管理员查询需使用 requireAdmin
import { requireAdmin } from '@/db/secure-db';
async function getAdminStats() {
const admin = await requireAdmin();
if (!admin) throw new Error('Admin required');
// 可查询所有用户数据
return db.select({ count: count() }).from(users);
}Design System
设计系统
Color Palette (Therapeutic)
治疗系配色方案
css
/* From globals.css */
--navy: #1a365d; /* Primary - trust, stability */
--teal: #319795; /* Secondary - calm, healing */
--coral: #ed8936; /* Accent - warmth, energy */
--cream: #fffaf0; /* Background - comfort */css
/* 来自 globals.css */
--navy: #1a365d; /* 主色调 - 信任、稳定 */
--teal: #319795; /* 辅助色 - 平静、治愈 */
--coral: #ed8936; /* 强调色 - 温暖、活力 */
--cream: #fffaf0; /* 背景色 - 舒适 */Time-Based Themes
基于时间的主题
typescript
function getTimeTheme(): 'dawn' | 'day' | 'dusk' | 'night' {
const hour = new Date().getHours();
if (hour >= 5 && hour < 9) return 'dawn';
if (hour >= 9 && hour < 17) return 'day';
if (hour >= 17 && hour < 21) return 'dusk';
return 'night';
}typescript
function getTimeTheme(): 'dawn' | 'day' | 'dusk' | 'night' {
const hour = new Date().getHours();
if (hour >= 5 && hour < 9) return 'dawn';
if (hour >= 9 && hour < 17) return 'day';
if (hour >= 17 && hour < 21) return 'dusk';
return 'night';
}Component Styling
组件样式
typescript
// Use Tailwind with design tokens
<Button
className="bg-navy hover:bg-navy/90 text-white"
variant="default"
>
Primary Action
</Button>
<Card className="bg-cream border-teal/20">
<CardContent className="text-navy">
Therapeutic content
</CardContent>
</Card>typescript
// 使用 Tailwind 结合设计令牌
<Button
className="bg-navy hover:bg-navy/90 text-white"
variant="default"
>
Primary Action
</Button>
<Card className="bg-cream border-teal/20">
<CardContent className="text-navy">
Therapeutic content
</CardContent>
</Card>Testing Pattern
测试模式
typescript
// src/lib/__tests__/feature.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
describe('Feature', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('loads and displays data', async () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
json: async () => ({ data: 'test' })
} as Response);
render(<FeatureComponent id="123" />);
await waitFor(() => {
expect(screen.getByText('test')).toBeInTheDocument();
});
});
it('handles errors gracefully', async () => {
vi.mocked(fetch).mockRejectedValueOnce(new Error('Network error'));
render(<FeatureComponent id="123" />);
await waitFor(() => {
expect(screen.getByText(/error/i)).toBeInTheDocument();
});
});
});typescript
// src/lib/__tests__/feature.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
describe('Feature', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('加载并展示数据', async () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
json: async () => ({ data: 'test' })
} as Response);
render(<FeatureComponent id="123" />);
await waitFor(() => {
expect(screen.getByText('test')).toBeInTheDocument();
});
});
it('优雅处理错误', async () => {
vi.mocked(fetch).mockRejectedValueOnce(new Error('Network error'));
render(<FeatureComponent id="123" />);
await waitFor(() => {
expect(screen.getByText(/error/i)).toBeInTheDocument();
});
});
});Error Handling
错误处理
typescript
// Use structured error responses
interface APIError {
error: string;
code?: string;
details?: unknown;
}
// In API routes
return NextResponse.json<APIError>(
{
error: 'Validation failed',
code: 'VALIDATION_ERROR',
details: zodError.issues
},
{ status: 400 }
);
// In components
try {
await submitData();
} catch (e) {
if (e instanceof APIError) {
toast.error(e.message);
} else {
toast.error('An unexpected error occurred');
console.error(e); // Log for debugging, not shown to user
}
}typescript
// 使用结构化错误响应
interface APIError {
error: string;
code?: string;
details?: unknown;
}
// 在API路由中
return NextResponse.json<APIError>(
{
error: 'Validation failed',
code: 'VALIDATION_ERROR',
details: zodError.issues
},
{ status: 400 }
);
// 在组件中
try {
await submitData();
} catch (e) {
if (e instanceof APIError) {
toast.error(e.message);
} else {
toast.error('An unexpected error occurred');
console.error(e); // 日志用于调试,不展示给用户
}
}Environment Variables
环境变量
Required variables (validated at startup):
bash
undefined启动时需验证的必填变量:
bash
undefinedAuthentication
认证
SESSION_SECRET= # 32+ random characters
SESSION_SECRET= # 32位以上随机字符
AI Integration
AI集成
ANTHROPIC_API_KEY= # Claude API key
ANTHROPIC_API_KEY= # Claude API密钥
Database
数据库
DATABASE_URL= # SQLite path or connection string
DATABASE_URL= # SQLite路径或连接字符串
Optional
可选
VAPID_PUBLIC_KEY= # Push notifications
VAPID_PRIVATE_KEY=
undefinedVAPID_PUBLIC_KEY= # 推送通知
VAPID_PRIVATE_KEY=
undefinedPre-Commit Checklist
提交前检查清单
Before committing any changes:
- passes
npm run lint - passes
npm run test - passes
npm run feature:validate - Feature manifest updated (if applicable)
- No secrets in code
- HIPAA compliance maintained (audit logs)
- Accessibility considered (semantic HTML, ARIA)
提交任何变更前,请确认:
- 执行通过
npm run lint - 执行通过
npm run test - 执行通过
npm run feature:validate - 业务特性清单已更新(如适用)
- 代码中无敏感信息
- 保持HIPAA合规(审计日志)
- 考虑无障碍访问(语义化HTML、ARIA)
Common Imports
常用导入
typescript
// Authentication
import { getSession, requireAuth } from '@/lib/auth';
// Database
import { db } from '@/db/secure-db';
import { eq, desc, and } from 'drizzle-orm';
// Audit
import { logPHIAccess, logSecurityEvent } from '@/lib/hipaa/audit';
// Rate limiting
import { createRateLimiter } from '@/lib/rate-limit';
// Validation
import { z } from 'zod';
// UI Components
import { Button } from '@/components/ui/Button';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { Input } from '@/components/ui/Input';typescript
// 认证
import { getSession, requireAuth } from '@/lib/auth';
// 数据库
import { db } from '@/db/secure-db';
import { eq, desc, and } from 'drizzle-orm';
// 审计
import { logPHIAccess, logSecurityEvent } from '@/lib/hipaa/audit';
// 限流
import { createRateLimiter } from '@/lib/rate-limit';
// 验证
import { z } from 'zod';
// UI组件
import { Button } from '@/components/ui/Button';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { Input } from '@/components/ui/Input';