191 lines
7.1 KiB
TypeScript
191 lines
7.1 KiB
TypeScript
import { defineSchema, defineTable } from "convex/server";
|
||
import { v } from "convex/values";
|
||
|
||
// 说明:
|
||
// - 这里先按“兼容现有 Next API 返回结构”的思路设计字段:id/workspace_id/user_id 等命名保持与 Supabase 一致。
|
||
// - Convex 自带的 _id 仍然存在,但我们暂时不把它暴露给上层业务,便于逐步迁移与回退。
|
||
|
||
export default defineSchema({
|
||
workspaces: defineTable({
|
||
id: v.string(),
|
||
name: v.string(),
|
||
type: v.union(v.literal("personal"), v.literal("team")),
|
||
icon_url: v.union(v.string(), v.null()),
|
||
created_by: v.string(),
|
||
created_at: v.string(),
|
||
})
|
||
.index("by_workspace_id", ["id"])
|
||
.index("by_created_by", ["created_by"]),
|
||
|
||
workspace_members: defineTable({
|
||
workspace_id: v.string(),
|
||
user_id: v.string(),
|
||
role: v.string(),
|
||
is_default: v.boolean(),
|
||
created_at: v.string(),
|
||
})
|
||
.index("by_user_id", ["user_id"])
|
||
.index("by_workspace_user", ["workspace_id", "user_id"])
|
||
.index("by_workspace_id", ["workspace_id"]),
|
||
|
||
documents: defineTable({
|
||
id: v.string(),
|
||
user_id: v.string(),
|
||
workspace_id: v.string(),
|
||
|
||
title: v.union(v.string(), v.null()),
|
||
parent_id: v.union(v.string(), v.null()),
|
||
sort_order: v.union(v.number(), v.null()),
|
||
is_starred: v.union(v.boolean(), v.null()),
|
||
is_template: v.boolean(),
|
||
access_scope: v.union(v.literal("private"), v.literal("shared"), v.literal("public")),
|
||
|
||
// 页面选项(对应 Supabase documents 上的 UI 配置列)
|
||
wide_layout: v.union(v.boolean(), v.null()),
|
||
use_small_text: v.union(v.boolean(), v.null()),
|
||
show_heading_numbers: v.union(v.boolean(), v.null()),
|
||
show_toc: v.union(v.boolean(), v.null()),
|
||
show_structure: v.union(v.boolean(), v.null()),
|
||
protect_editing: v.union(v.boolean(), v.null()),
|
||
show_word_count: v.union(v.boolean(), v.null()),
|
||
|
||
// 统计信息(由客户端编辑器计算后回写)
|
||
word_count: v.union(v.number(), v.null()),
|
||
character_count: v.union(v.number(), v.null()),
|
||
block_count: v.union(v.number(), v.null()),
|
||
|
||
// 说明:当前文档内容结构还在演进,先用 any 承接(与 Supabase Json 一致的宽松形态)。
|
||
content: v.any(),
|
||
|
||
// 说明:后续可用于搜索/索引(目前先留空,不强制写入)。
|
||
raw_text: v.optional(v.union(v.string(), v.null())),
|
||
|
||
// 时间戳统一使用 ISO 字符串,便于直接复用前端现有排序逻辑。
|
||
created_at: v.string(),
|
||
updated_at: v.union(v.string(), v.null()),
|
||
|
||
// 软删除(阶段 4 先不实现垃圾桶逻辑,但字段先留好,便于后续迁移)。
|
||
deleted_at: v.union(v.string(), v.null()),
|
||
deleted_by: v.union(v.string(), v.null()),
|
||
|
||
// 兼容旧逻辑:Supabase documents.mindmap_data。
|
||
mindmap_data: v.optional(v.any()),
|
||
})
|
||
.index("by_document_id", ["id"])
|
||
.index("by_user", ["user_id"])
|
||
.index("by_workspace", ["workspace_id"])
|
||
.index("by_workspace_parent", ["workspace_id", "parent_id"]),
|
||
|
||
// 最近访问(替代 Supabase user_recent_pages)
|
||
user_recent_pages: defineTable({
|
||
user_id: v.string(),
|
||
workspace_id: v.string(),
|
||
document_id: v.string(),
|
||
last_accessed_at: v.string(),
|
||
})
|
||
.index("by_user_workspace", ["user_id", "workspace_id"])
|
||
.index("by_user_document", ["user_id", "document_id"]),
|
||
|
||
// 阶段 5:异步任务/队列表(最小骨架,后续可扩展为通用作业系统)
|
||
jobs: defineTable({
|
||
id: v.string(),
|
||
user_id: v.string(),
|
||
type: v.string(),
|
||
status: v.union(v.literal("queued"), v.literal("running"), v.literal("succeeded"), v.literal("failed")),
|
||
payload: v.any(),
|
||
result: v.union(v.any(), v.null()),
|
||
error: v.union(v.string(), v.null()),
|
||
created_at: v.string(),
|
||
updated_at: v.string(),
|
||
started_at: v.union(v.string(), v.null()),
|
||
finished_at: v.union(v.string(), v.null()),
|
||
})
|
||
.index("by_job_id", ["id"])
|
||
.index("by_user", ["user_id"])
|
||
.index("by_status", ["status"]),
|
||
|
||
// 阶段 6:媒体/附件(替代 Supabase Storage + media_assets 表)
|
||
media_assets: defineTable({
|
||
id: v.string(),
|
||
workspace_id: v.string(),
|
||
document_id: v.string(),
|
||
|
||
asset_type: v.string(),
|
||
file_url: v.union(v.string(), v.null()),
|
||
thumbnail_url: v.union(v.string(), v.null()),
|
||
|
||
// Convex Files(storage)定位信息
|
||
// 说明:早期阶段 6(MinIO 直存)写入的记录可能没有该字段;允许缺失以便平滑迁移。
|
||
storage_id: v.optional(v.union(v.id("_storage"), v.null())),
|
||
|
||
// S3/MinIO 定位信息
|
||
bucket: v.union(v.string(), v.null()),
|
||
storage_path: v.union(v.string(), v.null()),
|
||
|
||
file_name: v.union(v.string(), v.null()),
|
||
file_size: v.union(v.number(), v.null()),
|
||
mime_type: v.union(v.string(), v.null()),
|
||
|
||
// OCR 相关(后续再接入)
|
||
ocr_text: v.union(v.string(), v.null()),
|
||
ocr_status: v.union(v.string(), v.null()),
|
||
ocr_payload: v.optional(v.any()),
|
||
ocr_strategy: v.optional(v.union(v.string(), v.null())),
|
||
|
||
// 回收站/清理
|
||
deleted_at: v.union(v.string(), v.null()),
|
||
deleted_by: v.union(v.string(), v.null()),
|
||
purged_at: v.union(v.string(), v.null()),
|
||
|
||
created_by: v.string(),
|
||
created_at: v.string(),
|
||
updated_at: v.string(),
|
||
})
|
||
.index("by_asset_id", ["id"])
|
||
.index("by_storage_id", ["storage_id"])
|
||
.index("by_workspace", ["workspace_id"])
|
||
.index("by_document", ["document_id"])
|
||
.index("by_workspace_deleted", ["workspace_id", "deleted_at"]),
|
||
|
||
// M2:思维导图(替代本地文件 public/documents/<docId>/mindmap*.json + Supabase documents.mindmap_data)
|
||
// 说明:
|
||
// - 业务侧依然以 (document_id, mindmap_id) 作为“定位键”,避免跨页面冲突。
|
||
// - id 是一个便于排查的全局唯一串(例如 `${docId}:${mindmapId}`),但不要求前端依赖它。
|
||
mindmaps: defineTable({
|
||
id: v.string(),
|
||
user_id: v.string(),
|
||
workspace_id: v.string(),
|
||
document_id: v.string(),
|
||
mindmap_id: v.string(),
|
||
data: v.any(),
|
||
created_at: v.string(),
|
||
updated_at: v.string(),
|
||
deleted_at: v.union(v.string(), v.null()),
|
||
deleted_by: v.union(v.string(), v.null()),
|
||
})
|
||
.index("by_mindmap_id", ["id"])
|
||
.index("by_doc_mindmap", ["document_id", "mindmap_id"])
|
||
.index("by_workspace", ["workspace_id"])
|
||
.index("by_workspace_deleted", ["workspace_id", "deleted_at"])
|
||
.index("by_user", ["user_id"]),
|
||
|
||
// M3:页面引用/反链(替代 Supabase RPC:record_page_ref、list_backlinks)
|
||
page_references: defineTable({
|
||
id: v.string(),
|
||
workspace_id: v.string(),
|
||
source_page_id: v.string(),
|
||
source_block_id: v.union(v.string(), v.null()),
|
||
target_page_id: v.string(),
|
||
alias: v.union(v.string(), v.null()),
|
||
display_mode: v.union(v.literal("inline"), v.literal("embed")),
|
||
is_previewable: v.boolean(),
|
||
created_by: v.string(),
|
||
created_at: v.string(),
|
||
updated_at: v.string(),
|
||
})
|
||
.index("by_reference_id", ["id"])
|
||
.index("by_workspace_target", ["workspace_id", "target_page_id"])
|
||
.index("by_workspace_source", ["workspace_id", "source_page_id"])
|
||
.index("by_unique", ["workspace_id", "source_page_id", "source_block_id", "target_page_id", "display_mode"]),
|
||
});
|