echo/supabase/migrations/202601120001_initial_schema.sql
ddshi 4c42d6b6f5 feat: 完成阶段 2 - 用户系统与智能体初始化
## 完成的功能
- 用户登录/注册(邮箱 + Google OAuth)
- 初始问题引导流程(4 个问题)
- 智能体自动生成(根据用户回答)
- 智能体列表页面

## 新增文件
- 登录/注册页面和组件
- Onboarding 页面和组件
- 智能体列表页面
- 智能体 API 端点
- 智能体 Prompt 设计

## 数据库迁移
- onboarding_fix 迁移(users.has_completed_onboarding)

## 测试
- 登录/注册:100% 通过
- Onboarding:85% 通过
- 智能体功能:85% 通过

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-12 14:08:12 +08:00

380 lines
16 KiB
PL/PgSQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- ============================================================================
-- Echo (回声) - 初始数据库 Schema 迁移脚本
-- 版本: 202601120001
-- 创建日期: 2026-01-12
-- ============================================================================
-- 启用 UUID 扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- ============================================================================
-- 第一部分: 公共函数定义
-- ============================================================================
-- 更新时间戳触发器函数
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
-- ============================================================================
-- 第二部分: 表创建(按依赖顺序)
-- ============================================================================
-- ---------------------------------------------------------------------------
-- 2.1 users - 用户表
-- ---------------------------------------------------------------------------
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
google_id VARCHAR(255) UNIQUE,
avatar_url TEXT,
language VARCHAR(10) DEFAULT 'zh-CN',
subscription_status VARCHAR(20) DEFAULT 'free',
subscription_expires_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 用户表索引
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_google_id ON users(google_id);
-- ---------------------------------------------------------------------------
-- 2.2 agents - 智能体表
-- ---------------------------------------------------------------------------
CREATE TABLE agents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
personality JSONB NOT NULL DEFAULT '{}',
background TEXT,
avatar_url TEXT,
is_custom BOOLEAN DEFAULT FALSE,
is_default BOOLEAN DEFAULT FALSE,
language VARCHAR(10) DEFAULT 'zh-CN',
system_prompt TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 智能体表索引
CREATE INDEX idx_agents_user_id ON agents(user_id);
CREATE INDEX idx_agents_default ON agents(user_id, is_default) WHERE is_custom = FALSE;
-- ---------------------------------------------------------------------------
-- 2.3 onboarding_answers - 初始问题回答表
-- ---------------------------------------------------------------------------
CREATE TABLE onboarding_answers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
question_1 TEXT NOT NULL,
question_2 TEXT NOT NULL,
question_3 TEXT NOT NULL,
question_4 TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 初始问题回答表索引
CREATE INDEX idx_onboarding_answers_user_id ON onboarding_answers(user_id);
-- ---------------------------------------------------------------------------
-- 2.4 letters - 信件表
-- ---------------------------------------------------------------------------
CREATE TABLE letters (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
content TEXT NOT NULL,
ai_reply TEXT,
status VARCHAR(20) DEFAULT 'draft',
scheduled_at TIMESTAMP WITH TIME ZONE,
replied_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 信件表索引
CREATE INDEX idx_letters_user_id ON letters(user_id);
CREATE INDEX idx_letters_agent_id ON letters(agent_id);
CREATE INDEX idx_letters_status ON letters(status);
CREATE INDEX idx_letters_scheduled ON letters(status, scheduled_at)
WHERE status = 'pending';
-- ---------------------------------------------------------------------------
-- 2.5 stamp_definitions - 邮票定义表
-- ---------------------------------------------------------------------------
CREATE TABLE stamp_definitions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(50) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
stamp_type VARCHAR(30) NOT NULL,
rarity VARCHAR(20) DEFAULT 'common',
image_url TEXT NOT NULL,
valid_from TIMESTAMP WITH TIME ZONE,
valid_until TIMESTAMP WITH TIME ZONE,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 邮票定义表索引
CREATE INDEX idx_stamp_definitions_code ON stamp_definitions(code);
CREATE INDEX idx_stamp_definitions_type ON stamp_definitions(stamp_type);
CREATE INDEX idx_stamp_definitions_active ON stamp_definitions(is_active);
-- ---------------------------------------------------------------------------
-- 2.6 user_stamps - 用户邮票表
-- ---------------------------------------------------------------------------
CREATE TABLE user_stamps (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
stamp_def_id UUID NOT NULL REFERENCES stamp_definitions(id),
source VARCHAR(30) NOT NULL,
letter_id UUID REFERENCES letters(id),
obtained_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
used_at TIMESTAMP WITH TIME ZONE
);
-- 用户邮票表索引
CREATE INDEX idx_user_stamps_user_id ON user_stamps(user_id);
CREATE INDEX idx_user_stamps_letter_id ON user_stamps(letter_id);
CREATE INDEX idx_user_stamps_unused ON user_stamps(user_id, used_at)
WHERE used_at IS NULL;
-- ---------------------------------------------------------------------------
-- 2.7 growth_tags - 成长标签表
-- ---------------------------------------------------------------------------
CREATE TABLE growth_tags (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
letter_id UUID NOT NULL REFERENCES letters(id) ON DELETE CASCADE,
mood_tags TEXT[] DEFAULT '{}',
topic_tags TEXT[] DEFAULT '{}',
behavior_tags TEXT[] DEFAULT '{}',
sentiment_score DECIMAL(3,2),
keywords TEXT[] DEFAULT '{}',
ai_analysis TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 成长标签表索引
CREATE INDEX idx_growth_tags_user_id ON growth_tags(user_id);
CREATE INDEX idx_growth_tags_letter_id ON growth_tags(letter_id);
CREATE INDEX idx_growth_tags_mood ON growth_tags USING GIN (mood_tags);
CREATE INDEX idx_growth_tags_topic ON growth_tags USING GIN (topic_tags);
-- ---------------------------------------------------------------------------
-- 2.8 milestones - 里程碑定义表
-- ---------------------------------------------------------------------------
CREATE TABLE milestones (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(50) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
condition_type VARCHAR(30) NOT NULL,
condition_value INTEGER NOT NULL,
reward_stamp_def_id UUID REFERENCES stamp_definitions(id),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 里程碑定义表索引
CREATE INDEX idx_milestones_code ON milestones(code);
CREATE INDEX idx_milestones_active ON milestones(is_active);
-- ---------------------------------------------------------------------------
-- 2.9 achievements - 用户成就表
-- ---------------------------------------------------------------------------
CREATE TABLE achievements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
milestone_id UUID NOT NULL REFERENCES milestones(id),
achieved_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
notification_sent BOOLEAN DEFAULT FALSE,
UNIQUE(user_id, milestone_id)
);
-- 用户成就表索引
CREATE INDEX idx_achievements_user_id ON achievements(user_id);
CREATE INDEX idx_achievements_milestone_id ON achievements(milestone_id);
-- ---------------------------------------------------------------------------
-- 2.10 daily_stats - 每日统计表
-- ---------------------------------------------------------------------------
CREATE TABLE daily_stats (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
stat_date DATE NOT NULL,
letters_sent INTEGER DEFAULT 0,
stamps_used INTEGER DEFAULT 0,
stamps_granted INTEGER DEFAULT 0,
last_active_at TIMESTAMP WITH TIME ZONE,
UNIQUE(user_id, stat_date)
);
-- 每日统计表索引
CREATE INDEX idx_daily_stats_user_date ON daily_stats(user_id, stat_date);
-- ============================================================================
-- 第三部分: 自动更新时间戳触发器
-- ============================================================================
CREATE TRIGGER update_users_updated_at
BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_agents_updated_at
BEFORE UPDATE ON agents
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_letters_updated_at
BEFORE UPDATE ON letters
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- 第四部分: Row Level Security (RLS) 策略
-- ============================================================================
-- 4.1 users - 用户表 RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own profile" ON users
FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Users can update own profile" ON users
FOR UPDATE USING (auth.uid() = id);
-- 4.2 agents - 智能体表 RLS
ALTER TABLE agents ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own agents" ON agents
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can create agents" ON agents
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own agents" ON agents
FOR UPDATE USING (auth.uid() = user_id);
CREATE POLICY "Users can delete own agents" ON agents
FOR DELETE USING (auth.uid() = user_id);
-- 4.3 onboarding_answers - 初始问题回答表 RLS
ALTER TABLE onboarding_answers ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own onboarding answers" ON onboarding_answers
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can create onboarding answers" ON onboarding_answers
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own onboarding answers" ON onboarding_answers
FOR UPDATE USING (auth.uid() = user_id);
-- 4.4 letters - 信件表 RLS
ALTER TABLE letters ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own letters" ON letters
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can create letters" ON letters
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own letters" ON letters
FOR UPDATE USING (auth.uid() = user_id);
CREATE POLICY "Users can delete own letters" ON letters
FOR DELETE USING (auth.uid() = user_id);
-- 4.5 stamp_definitions - 邮票定义表 RLS公开查询
ALTER TABLE stamp_definitions ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Anyone can view stamp definitions" ON stamp_definitions
FOR SELECT USING (true);
-- 4.6 user_stamps - 用户邮票表 RLS
ALTER TABLE user_stamps ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own stamps" ON user_stamps
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "System can update user stamps" ON user_stamps
FOR UPDATE USING (auth.role() = 'service_role');
CREATE POLICY "System can insert user stamps" ON user_stamps
FOR INSERT WITH CHECK (auth.role() = 'service_role');
-- 4.7 growth_tags - 成长标签表 RLS
ALTER TABLE growth_tags ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own growth tags" ON growth_tags
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "System can create growth tags" ON growth_tags
FOR INSERT WITH CHECK (auth.role() = 'service_role');
CREATE POLICY "System can update growth tags" ON growth_tags
FOR UPDATE USING (auth.role() = 'service_role');
-- 4.8 milestones - 里程碑定义表 RLS公开查询
ALTER TABLE milestones ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Anyone can view milestones" ON milestones
FOR SELECT USING (true);
-- 4.9 achievements - 用户成就表 RLS
ALTER TABLE achievements ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own achievements" ON achievements
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "System can create achievements" ON achievements
FOR INSERT WITH CHECK (auth.role() = 'service_role');
-- 4.10 daily_stats - 每日统计表 RLS
ALTER TABLE daily_stats ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own daily stats" ON daily_stats
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "System can update daily stats" ON daily_stats
FOR UPDATE USING (auth.role() = 'service_role');
CREATE POLICY "System can insert daily stats" ON daily_stats
FOR INSERT WITH CHECK (auth.role() = 'service_role');
-- ============================================================================
-- 第五部分: 初始数据插入
-- ============================================================================
-- 5.1 邮票定义初始数据
INSERT INTO stamp_definitions (code, name, description, stamp_type, rarity, image_url, is_active) VALUES
('daily_default', '每日邮票', '每日自动发放的基础邮票', 'daily', 'common', '/stamps/daily.png', TRUE),
('welcome', '初遇回声', '完成注册,获得的第一个成就', 'achievement', 'rare', '/stamps/welcome.png', TRUE),
('first_letter', '第一封信', '成功发送第一封信件', 'achievement', 'rare', '/stamps/first_letter.png', TRUE),
('week_streak', '持续对话', '连续 7 天写信', 'achievement', 'epic', '/stamps/week_streak.png', TRUE),
('month_streak', '一月相伴', '连续 30 天写信', 'achievement', 'legendary', '/stamps/month_streak.png', TRUE),
('spring_2025', '春日限定 2025', '2025 春季活动限定邮票', 'limited', 'epic', '/stamps/spring_2025.png', TRUE),
('collector_10', '集邮家', '收集 10 种不同邮票', 'achievement', 'rare', '/stamps/collector_10.png', TRUE),
('collector_50', '邮票猎人', '收集 50 种不同邮票', 'achievement', 'epic', '/stamps/collector_50.png', TRUE);
-- 5.2 里程碑定义初始数据
INSERT INTO milestones (code, name, description, condition_type, condition_value, reward_stamp_def_id, is_active) VALUES
('welcome', '初遇回声', '完成注册,开启回声之旅', 'registration', 1, (SELECT id FROM stamp_definitions WHERE code = 'welcome'), TRUE),
('first_letter', '第一封信', '成功发送第一封信给未来的自己', 'letter_count', 1, (SELECT id FROM stamp_definitions WHERE code = 'first_letter'), TRUE),
('week_streak', '持续对话', '连续 7 天写信', 'consecutive_days', 7, (SELECT id FROM stamp_definitions WHERE code = 'week_streak'), TRUE),
('month_streak', '一月相伴', '连续 30 天写信', 'consecutive_days', 30, (SELECT id FROM stamp_definitions WHERE code = 'month_streak'), TRUE),
('letters_10', '十封信', '累计发送 10 封信', 'total_letters', 10, NULL, TRUE),
('letters_50', '五十封信', '累计发送 50 封信', 'total_letters', 50, NULL, TRUE),
('letters_100', '百封信', '累计发送 100 封信', 'total_letters', 100, NULL, TRUE),
('collector_10', '集邮家初级', '收集 10 种不同邮票', 'unique_stamps', 10, (SELECT id FROM stamp_definitions WHERE code = 'collector_10'), TRUE),
('collector_50', '集邮家高级', '收集 50 种不同邮票', 'unique_stamps', 50, (SELECT id FROM stamp_definitions WHERE code = 'collector_50'), TRUE);
-- ============================================================================
-- 迁移完成
-- ============================================================================