Files
mnote/wolai-frontend/convex/schema.ts
T
2026-01-17 10:12:53 +08:00

191 lines
7.1 KiB
TypeScript
Raw 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.
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 Filesstorage)定位信息
// 说明:早期阶段 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 RPCrecord_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"]),
});