recovery-coach-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Recovery 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 scripts
src/
├── 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
undefined

Authentication

认证

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=
undefined
VAPID_PUBLIC_KEY= # 推送通知 VAPID_PRIVATE_KEY=
undefined

Pre-Commit Checklist

提交前检查清单

Before committing any changes:
  1. npm run lint
    passes
  2. npm run test
    passes
  3. npm run feature:validate
    passes
  4. Feature manifest updated (if applicable)
  5. No secrets in code
  6. HIPAA compliance maintained (audit logs)
  7. Accessibility considered (semantic HTML, ARIA)
提交任何变更前,请确认:
  1. npm run lint
    执行通过
  2. npm run test
    执行通过
  3. npm run feature:validate
    执行通过
  4. 业务特性清单已更新(如适用)
  5. 代码中无敏感信息
  6. 保持HIPAA合规(审计日志)
  7. 考虑无障碍访问(语义化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';