Files
mnote/wolai-frontend/convex/blocks.ts
T

169 lines
4.4 KiB
TypeScript
Raw Normal View History

2026-04-13 19:21:42 +08:00
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 };
},
});