Supabase 数据库技能
自信地导航和查询 Empathy Ledger Supabase 数据库。
数据库关系图
┌─────────────────────────────────────────────────────────────────────────────┐
│ TENANTS (顶级) │
│ │ │
│ ┌───────────────────────────────┼───────────────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ organisations │◄──────────│ profiles │──────────►│ tenant_members │ │
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
│ │ │ │
│ │ │ is_storyteller │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ projects │◄──────────│ stories │ │
│ └──────────────┘ └──────────────┘ │
│ │ │ │
│ │ ├────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ transcripts │ │media_assets │ │story_distribs│ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ │ │ ▼ │
│ ▼ ▼ ┌──────────────┐ │
│ ┌──────────────┐ ┌──────────────┐ │ embed_tokens │ │
│ │ key_quotes[] │ │media_usage │ └──────────────┘ │
│ │ themes[] │ │_tracking │ │
│ │ ai_summary │ └──────────────┘ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
完整表清单
Live Supabase: 165 个对象 (153 个表, 7 个视图, 3 个分区, 2 个系统) 迁移定义的: 71 个表 带 TypeScript 类型的: 35 个表
另见: DATABASE_ALIGNMENT_AUDIT.md
⚠️ 架构漂移警告: ~80 个表存在于 Supabase 但没有迁移文件。 使用
npx supabase gen types typescript --local生成准确的类型。
1. 身份与访问 (12 个表)
| 表 | 用途 | 有类型 |
|---|---|---|
tenants |
顶级多租户隔离 | ✅ |
profiles |
用户账户 (与 auth.users 同步) | ✅ |
organisations |
社区团体与等级/政策 | ✅ |
organization_members |
用户 ↔ 组织成员关系 | ✅ |
organization_roles |
组织内 RBAC 角色 | ⚠️ |
organization_invitations |
待处理邀请 | ⚠️ |
tenant_members |
用户 ↔ 租户成员关系 | ✅ |
profile_organizations |
个人资料-组织联接 | ✅ |
profile_locations |
用户位置 | ✅ |
profile_projects |
用户-项目联接 | ✅ |
user_sessions |
会话跟踪 | ✅ |
user_reports |
用户报告 | ✅ |
2. 项目与上下文 (9 个表)
| 表 | 用途 | 有类型 |
|---|---|---|
projects |
故事集合 | ✅ |
project_participants |
项目成员 | ✅ |
project_contexts |
AI 提取的项目上下文 | ⚠️ |
organization_contexts |
AI 提取的组织上下文 | ⚠️ |
project_profiles |
扩展项目元数据 | ⚠️ |
project_seed_interviews |
种子访谈数据 | ⚠️ |
project_analyses |
缓存的 AI 分析 | ⚠️ |
seed_interview_templates |
访谈模板 | ⚠️ |
development_plans |
用户发展计划 | ✅ |
3. 故事与内容 (10 个表)
| 表 | 用途 | 有类型 |
|---|---|---|
stories |
核心故事内容 | ✅ |
transcripts |
音频/文本转录 | ✅ |
media_assets |
图像、视频、音频 | ✅ |
media_usage_tracking |
媒体访问跟踪 | ✅ |
extracted_quotes |
AI 提取的引用 | ✅ |
transcription_jobs |
转录队列 | ⚠️ |
media_import_sessions |
批量导入跟踪 | ⚠️ |
title_suggestions |
AI 标题建议 | ⚠️ |
galleries |
照片画廊 | ✅ |
gallery_photos |
画廊项目 | ✅ |
4. 分发与联合 (11 个表)
| 表 | 用途 | 有类型 |
|---|---|---|
story_distributions |
外部平台跟踪 | ✅ |
story_access_tokens |
临时分享链接 (可撤销,限时) | ✅ |
embed_tokens |
安全嵌入令牌 | ✅ |
story_syndication_consent |
合作伙伴同意记录 | ⚠️ |
external_applications |
合作伙伴应用注册 | ⚠️ |
story_access_log |
外部访问日志 | ⚠️ |
webhook_subscriptions |
合作伙伴 webhooks | ⚠️ |
webhook_delivery_log |
Webhook 尝试 | ⚠️ |
consent_change_log |
同意审计跟踪 | ⚠️ |
consent_proofs |
GDPR 同意证明 | ⚠️ |
story_review_invitations |
故事讲述者审核链接 | ⚠️ |
5. 合作伙伴门户 (6 个表)
| 表 | 用途 | 有类型 |
|---|---|---|
partner_projects |
合作伙伴策划的项目 | ⚠️ |
story_syndication_requests |
内容请求 | ⚠️ |
partner_messages |
合作伙伴-故事讲述者消息 | ⚠️ |
partner_team_members |
合作伙伴团队访问 | ⚠️ |
partner_analytics_daily |
合作伙伴分析 | ⚠️ |
partner_message_templates |
消息模板 | ⚠️ |
6. 分析与洞察 (17 个表)
| 表 | 用途 | 有类型 |
|---|---|---|
storyteller_analytics |
聚合的故事讲述者统计 | ⚠️ |
narrative_themes |
平台范围的主题 | ⚠️ |
storyteller_themes |
每个故事讲述者的主题 | ⚠️ |
storyteller_quotes |
有影响力的引用 | ⚠️ |
storyteller_connections |
网络连接 | ⚠️ |
storyteller_demographics |
人口统计数据 | ⚠️ |
storyteller_recommendations |
AI 推荐 | ❌ ORPHANED |
storyteller_dashboard_config |
仪表板偏好设置 | ⚠️ |
storyteller_milestones |
成就 | ⚠️ |
storyteller_engagement |
参与度指标 | ⚠️ |
storyteller_impact_metrics |
影响跟踪 | ⚠️ |
cross_narrative_insights |
跨故事洞察 | ❌ ORPHANED |
cross_sector_insights |
行业分析 | ⚠️ |
geographic_impact_patterns |
地理模式 | ❌ ORPHANED |
theme_evolution_tracking |
主题趋势 | ⚠️ |
analytics_processing_jobs |
分析作业队列 | ❌ ORPHANED |
platform_analytics |
平台范围的统计 | ⚠️ |
7. 参与度跟踪 (2 个表)
| 表 | 用途 | 有类型 |
|---|---|---|
story_engagement_events |
每次查看事件 | ⚠️ |
story_engagement_daily |
每日汇总 | ⚠️ |
8. AI & 安全 (9 个表)
| 表 | 用途 | 有类型 |
|---|---|---|
ai_usage_events |
AI 成本/使用跟踪 | ⚠️ |
tenant_ai_policies |
每个租户的 AI 限制 | ⚠️ |
ai_agent_registry |
AI 代理配置 | ⚠️ |
ai_usage_daily |
每日 AI 汇总 | ⚠️ |
elder_review_queue |
长者审核工作流 | ⚠️ |
moderation_results |
审核决策 | ⚠️ |
moderation_appeals |
上诉请求 | ⚠️ |
ai_moderation_logs |
AI 审核日志 | ⚠️ |
ai_safety_logs |
安全检查日志 | ⚠️ |
9. 管理 & 系统 (8 个表)
| 表 | 用途 | 有类型 |
|---|---|---|
audit_logs |
合规审计跟踪 | ✅ |
deletion_requests |
GDPR 删除队列 | ✅ |
activity_log |
管理活动反馈 | ⚠️ |
notifications |
应用内通知 | ⚠️ |
admin_messages |
管理广播 | ⚠️ |
message_recipients |
消息传递 | ⚠️ |
ai_analysis_jobs |
AI 作业队列 | ⚠️ |
platform_stats_cache |
缓存的平台统计 | ⚠️ |
10. 世界巡回 (3 个表)
| 表 | 用途 | 有类型 |
|---|---|---|
tour_requests |
巡回访问请求 | ⚠️ |
tour_stops |
已完成的巡回站点 | ⚠️ |
dream_organizations |
目标组织 | ⚠️ |
11. 文化与影响 (5 个表)
| 表 | 用途 | 有类型 |
|---|---|---|
cultural_protocols |
文化指南 | ✅ |
cultural_tags |
文化标签 | ✅ |
community_impact_insights |
影响时刻 | ✅ |
community_impact_metrics |
聚合的影响 | ✅ |
live_community_narratives |
自动生成的叙述 | ✅ |
locations |
地理位置 | ✅ |
events |
事件跟踪 | ✅ |
12. 附加表 (在 Supabase 中,没有迁移)
这些表存在于 Supabase 但没有任何迁移文件:
| 表 | 用途 | 有类型 |
|---|---|---|
activities |
活动跟踪 (52 列!) | ❌ |
outcomes |
结果跟踪 (38 列) | ❌ |
annual_reports |
年度报告 | ❌ |
annual_report_stories |
报告-故事链接 | ❌ |
report_sections |
报告部分 | ❌ |
report_templates |
报告模板 | ❌ |
blog_posts |
博客内容 | ❌ |
testimonials |
用户推荐 | ❌ |
services |
服务定义 | ❌ |
service_impact |
服务影响指标 | ❌ |
partners |
合作伙伴组织 | ❌ |
team_members |
团队成员档案 | ❌ |
13. 照片系统 (仅在 Supabase 中)
| 表 | 用途 | 有类型 |
|---|---|---|
photo_analytics |
照片查看跟踪 | ❌ |
photo_faces |
面部识别数据 | ❌ |
photo_galleries |
照片画廊 | ❌ |
photo_gallery_items |
画廊项目 | ❌ |
photo_locations |
照片位置 | ❌ |
photo_memories |
照片记忆 | ❌ |
photo_organizations |
照片组织链接 | ❌ |
photo_projects |
照片项目链接 | ❌ |
photo_storytellers |
照片故事讲述者链接 | ❌ |
photo_tags |
照片标签 | ❌ |
14. 遗留/同步表
| 表 | 用途 | 有类型 |
|---|---|---|
empathy_entries |
遗留的同理心数据 | ❌ |
empathy_sync_log |
同步跟踪 | ❌ |
syndicated_stories |
联合内容 | ❌ |
scraped_services |
网络刮削数据 | ❌ |
scraper_health_metrics |
刮削器健康 | ❌ |
scraping_metadata |
刮削元数据 | ❌ |
⚠️ 拼写注释
Supabase 使用 organizations (美国拼写)
TypeScript 类型使用 organisations (英国拼写)
查询时使用 Supabase 的拼写。类型可能需要更新。
外键关系
故事表 (中心枢纽)
stories.storyteller_id → profiles.id // 谁讲述了这个故事
stories.author_id → profiles.id // 谁创作/记录了
stories.project_id → projects.id // 属于哪个项目
stories.organization_id → organisations.id // 哪个组织拥有
stories.tenant_id → tenants.id // 租户隔离
stories.featured_media_id → media_assets.id // 封面图片
转录表
transcripts.storyteller_id → profiles.id // 谁在说话
transcripts.tenant_id → tenants.id // 租户隔离
// 注意: 故事可以通过内容或 transcript_id 链接到转录
组织层级
tenants.organization_id → organisations.id // 租户的主要组织
organisations.tenant_id → tenants.id // 租户所有权
organization_members.profile_id → profiles.id // 用户
organization_members.organization_id → organisations.id // 组织
分发链
story_distributions.story_id → stories.id // 哪个故事
story_distributions.tenant_id → tenants.id // 租户隔离
embed_tokens.story_id → stories.id // 哪个故事
embed_tokens.distribution_id → story_distributions.id // 父级分发
story_access_tokens.story_id → stories.id // 哪个故事 (临时分享链接)
story_access_tokens.created_by → profiles.id // 谁创建了链接
story_access_tokens.tenant_id → tenants.id // 租户隔离
按领域分类的类型文件
| 领域 | 类型文件 | 覆盖的表 |
|---|---|---|
| 用户 | src/types/database/user-profile.ts |
profiles, profile_locations, profile_organizations, user_sessions |
| 组织 | src/types/database/organization-tenant.ts |
organisations, organization_members, tenants, tenant_members |
| 项目 | src/types/database/project-management.ts |
projects, project_participants |
| 内容 | src/types/database/content-media.ts |
stories, transcripts, media_assets, extracted_quotes |
| 分发 | src/types/database/story-ownership.ts |
story_distributions, embed_tokens, audit_logs, deletion_requests |
| 共享控制 | src/types/database/story-access-tokens.ts |
story_access_tokens |
| 文化 | src/types/database/cultural-sensitivity.ts |
cultural_safety_moderation |
| 位置 | src/types/database/location-events.ts |
locations, events |
| 分析 | src/types/database/analysis-support.ts |
transcript_analysis, themes, quotes |
Supabase 客户端使用
客户端类型
// 浏览器客户端 (使用 cookie, 遵守 RLS)
import { createSupabaseBrowserClient } from '@/lib/supabase/client'
const supabase = createSupabaseBrowserClient()
// 服务器 SSR 客户端 (用于 API 路由, 服务器组件)
import { createSupabaseServerClient } from '@/lib/supabase/client-ssr'
const supabase = createSupabaseServerClient()
// 服务角色客户端 (绕过 RLS - 仅限管理员!)
import { createSupabaseServiceClient } from '@/lib/supabase/service-role-client'
const supabase = createSupabaseServiceClient()
何时使用每个客户端
| 客户端 | 用例 | RLS | 认证 |
|---|---|---|---|
| 浏览器 | React 组件 | 是 | 用户会话 |
| 服务器 SSR | API 路由, 服务器组件 | 是 | 用户会话 |
| 服务角色 | 管理操作, 后台作业 | 否 | 服务密钥 |
常见查询模式
获取带有故事讲述者的故事
const { data } = await supabase
.from('stories')
.select(`
*,
storyteller:profiles!stories_storyteller_id_fkey(
id, display_name, profile_image_url
)
`)
.eq('status', 'published')
.eq('tenant_id', tenantId)
获取带有主题的转录
const { data } = await supabase
.from('transcripts')
.select('id, title, themes, key_quotes, ai_summary')
.not('themes', 'is', null)
.order('created_at', { ascending: false })
获取带有成员的组织
const { data } = await supabase
.from('organisations')
.select(`
*,
members:organization_members(
profile:profiles(id, display_name, profile_image_url),
role
)
`)
.eq('id', orgId)
.single()
获取所有关系的故事
const { data } = await supabase
.from('stories')
.select(`
*,
storyteller:profiles!stories_storyteller_id_fkey(*),
project:projects(*),
organization:organisations(*),
distributions:story_distributions(*),
featured_media:media_assets(*)
`)
.eq('id', storyId)
.single()
基于主题的故事搜索 (数组重叠)
// 与任何匹配主题的故事
const { data } = await supabase
.from('stories')
.select('*')
.overlaps('ai_themes', ['identity', 'heritage'])
// 与所有主题匹配的故事
const { data } = await supabase
.from('stories')
.select('*')
.contains('ai_themes', ['identity', 'heritage'])
按状态计数
const { count } = await supabase
.from('stories')
.select('*', { count: 'exact', head: true })
.eq('status', 'published')
.eq('tenant_id', tenantId)
故事访问令牌 (共享控制)
验证令牌并获取故事
// 使用数据库函数进行验证 + 查看计数增加
const { data: validation } = await supabase.rpc('validate_and_increment_token', {
p_token: 'abc123xyz'
})
if (validation[0]?.is_valid) {
const { data: story } = await supabase
.from('stories')
.select('*, storyteller:profiles(*)')
.eq('id', validation[0].story_id)
.single()
}
获取故事的活跃共享链接
const { data: tokens } = await supabase
.from('story_access_tokens')
.select('*')
.eq('story_id', storyId)
.eq('revoked', false)
.gt('expires_at', new Date().toISOString())
.order('created_at', { ascending: false })
创建共享链接
import { nanoid } from 'nanoid'
const token = nanoid(21)
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 天
const { data } = await supabase
.from('story_access_tokens')
.insert({
story_id: storyId,
token,
expires_at: expiresAt.toISOString(),
purpose: 'social-media',
created_by: userId,
tenant_id: tenantId
})
.select()
.single()
const shareUrl = `https://empathy-ledger.org/s/${token}`
撤销共享链接
const { error } = await supabase
.from('story_access_tokens')
.update({ revoked: true })
.eq('id', tokenId)
.eq('story_id', storyId) // 确保用户拥有故事
获取共享分析
// 查看计数, 最多共享的故事
const { data: analytics } = await supabase
.from('story_access_tokens')
.select('story_id, view_count, purpose, shared_to')
.eq('story_id', storyId)
.order('view_count', { ascending: false })
多租户查询模式
重要: 所有查询始终通过 tenant_id 进行数据隔离。
// 标准查询模式
async function getStories(userId: string) {
const supabase = createSupabaseServerClient()
// 1. 获取用户的租户
const { data: profile } = await supabase
.from('profiles')
.select('tenant_id')
.eq('id', userId)
.single()
// 2. 使用租户过滤器查询
const { data } = await supabase
.from('stories')
.select('*')
.eq('tenant_id', profile.tenant_id) // 始终包含!
.eq('status', 'published')
return data
}
数据库函数
可用的 RPC 函数:
// 计算租户分析
const { data } = await supabase.rpc('calculate_tenant_analytics', {
tenant_uuid: tenantId
})
// 获取组织统计
const { data } = await supabase.rpc('get_organization_stats', {
org_id: orgId
})
// 全文搜索引用
const { data } = await supabase.rpc('search_quotes', {
query: 'wisdom ancestors'
})
// 搜索媒体
const { data } = await supabase.rpc('search_media', {
query: 'interview video'
})
迁移位置
所有数据库架构在: supabase/migrations/
关键迁移:
20251220093000_multi_org_tenants.sql- 多组织租户结构20251207_story_ownership_distribution.sql- 分发系统20251209000000_cultural_safety_moderation_tables.sql- 文化安全20251210000000_partner_portal_system.sql- 合作伙伴分发
何时使用这个技能
调用时:
- 需要了解表关系
- 编写 Supabase 查询
- 查找正确的类型定义
- 理解外键约束
- 调试数据访问问题
- 实施涉及数据库的新功能
MCP 访问
这个项目配置了 MCP 用于直接访问 Supabase:
只读 (默认):
https://mcp.supabase.com/mcp?project_ref=yvnuayzslukamizrlhwb&read_only=true
带写入访问:
https://mcp.supabase.com/mcp?project_ref=yvnuayzslukamizrlhwb&features=database,docs,debugging,development,functions,branching
可用的 MCP 工具:
list_tables- 查看所有表和列execute_sql- 运行 SQL 查询list_migrations- 查看迁移历史generate_typescript_types- 从架构生成类型get_logs- 查看应用日志
触发器: 用户询问数据库表、关系、查询或 “如何从 Supabase 获取 X”