import { mutation, query } from "./_generated/server"; import { v } from "convex/values"; import { nowIso } from "./_utils/time"; import type { MutationCtx, QueryCtx } from "./_generated/server"; async function assertWorkspaceMember( ctx: QueryCtx | MutationCtx, userId: string, workspaceId: string, ) { const membership = await ctx.db .query("workspace_members") .withIndex("by_workspace_user", (q) => q.eq("workspace_id", workspaceId).eq("user_id", userId), ) .first(); if (!membership) { throw new Error("无权访问该工作空间"); } return membership; } export const getById = query({ args: { userId: v.string(), workspaceId: v.string(), blockId: v.string(), }, handler: async (ctx, args) => { await assertWorkspaceMember(ctx, args.userId, args.workspaceId); return await ctx.db .query("blocks") .withIndex("by_block_id", (q) => q.eq("id", args.blockId)) .first(); }, }); export const listByPage = query({ args: { userId: v.string(), workspaceId: v.string(), pageId: v.string(), }, handler: async (ctx, args) => { await assertWorkspaceMember(ctx, args.userId, args.workspaceId); return await ctx.db .query("blocks") .withIndex("by_page", (q) => q.eq("page_id", args.pageId)) .collect(); }, }); export const append = mutation({ args: { userId: v.string(), workspaceId: v.string(), pageId: v.string(), blocks: v.array( v.object({ id: v.string(), parentBlockId: v.union(v.string(), v.null()), type: v.string(), props: v.optional(v.any()), content: v.optional(v.any()), orderKey: v.string(), }), ), }, handler: async (ctx, args) => { await assertWorkspaceMember(ctx, args.userId, args.workspaceId); const ts = nowIso(); for (const block of args.blocks) { await ctx.db.insert("blocks", { id: block.id, workspace_id: args.workspaceId, page_id: args.pageId, parent_block_id: block.parentBlockId, type: block.type, props: block.props, content: block.content, child_block_ids: [], created_by: args.userId, created_at: ts, updated_at: ts, }); } return { ok: true, count: args.blocks.length }; }, }); export const insertAfter = mutation({ args: { userId: v.string(), workspaceId: v.string(), pageId: v.string(), afterBlockId: v.string(), block: v.object({ id: v.string(), parentBlockId: v.union(v.string(), v.null()), type: v.string(), props: v.optional(v.any()), content: v.optional(v.any()), orderKey: v.string(), }), }, handler: async (ctx, args) => { await assertWorkspaceMember(ctx, args.userId, args.workspaceId); const ts = nowIso(); await ctx.db.insert("blocks", { id: args.block.id, workspace_id: args.workspaceId, page_id: args.pageId, parent_block_id: args.block.parentBlockId, type: args.block.type, props: args.block.props, content: args.block.content, child_block_ids: [], created_by: args.userId, created_at: ts, updated_at: ts, }); return { ok: true, blockId: args.block.id, afterBlockId: args.afterBlockId }; }, }); export const replace = mutation({ args: { userId: v.string(), workspaceId: v.string(), blockId: v.string(), type: v.string(), props: v.optional(v.any()), content: v.optional(v.any()), }, handler: async (ctx, args) => { await assertWorkspaceMember(ctx, args.userId, args.workspaceId); const block = await ctx.db .query("blocks") .withIndex("by_block_id", (q) => q.eq("id", args.blockId)) .first(); if (!block) throw new Error("块不存在"); await ctx.db.patch(block._id, { type: args.type, props: args.props, content: args.content, updated_at: nowIso(), }); return { ok: true }; }, }); export const remove = mutation({ args: { userId: v.string(), workspaceId: v.string(), blockId: v.string(), }, handler: async (ctx, args) => { await assertWorkspaceMember(ctx, args.userId, args.workspaceId); const block = await ctx.db .query("blocks") .withIndex("by_block_id", (q) => q.eq("id", args.blockId)) .first(); if (!block) throw new Error("块不存在"); await ctx.db.delete(block._id); return { ok: true }; }, });