Files
mnote/wolai-frontend/convex/groups.ts
T
2026-01-24 12:32:51 +08:00

206 lines
6.2 KiB
TypeScript

import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
import { getAuthUserId } from "@convex-dev/auth/server";
import { nowIso } from "./_utils/time";
import type { Id } from "./_generated/dataModel";
async function requireUserId(ctx: any): Promise<string> {
const userId = await getAuthUserId(ctx);
if (userId === null) throw new Error("未登录");
return String(userId);
}
async function requireWorkspaceMember(ctx: any, workspaceId: string, userId: string) {
const member = await ctx.db
.query("workspace_members")
.withIndex("by_workspace_user", (q: any) => q.eq("workspace_id", workspaceId).eq("user_id", userId))
.first();
if (!member) throw new Error("无权限");
return member;
}
function normalizeGroupName(raw: string): string {
const name = String(raw ?? "").trim();
if (!name) throw new Error("群组名称不能为空");
if (name.length > 50) throw new Error("群组名称最多 50 个字符");
return name;
}
export const listByWorkspace = query({
args: { workspaceId: v.string() },
handler: async (ctx, args) => {
const userId = await requireUserId(ctx);
await requireWorkspaceMember(ctx, args.workspaceId, userId);
const groups = await ctx.db
.query("groups")
.withIndex("by_workspace", (q: any) => q.eq("workspace_id", args.workspaceId))
.collect();
groups.sort((a: any, b: any) => (a.created_at ?? "").localeCompare(b.created_at ?? ""));
return groups.map((g: any) => ({
id: g.id,
workspace_id: g.workspace_id,
name: g.name,
created_by: g.created_by,
created_at: g.created_at,
updated_at: g.updated_at,
}));
},
});
export const listMineByWorkspace = query({
args: { workspaceId: v.string() },
handler: async (ctx, args) => {
const userId = await requireUserId(ctx);
await requireWorkspaceMember(ctx, args.workspaceId, userId);
const memberships = await ctx.db
.query("group_members")
.withIndex("by_workspace_user", (q: any) => q.eq("workspace_id", args.workspaceId).eq("user_id", userId))
.collect();
const groupsById = new Map<string, any>();
for (const m of memberships) {
const group = await ctx.db
.query("groups")
.withIndex("by_group_id", (q: any) => q.eq("id", m.group_id))
.first();
if (group) {
groupsById.set(String(group.id), {
id: group.id,
workspace_id: group.workspace_id,
name: group.name,
created_by: group.created_by,
created_at: group.created_at,
updated_at: group.updated_at,
my_role: m.role,
});
}
}
const rows = Array.from(groupsById.values());
rows.sort((a: any, b: any) => String(a.created_at ?? "").localeCompare(String(b.created_at ?? "")));
// 额外:带上当前用户用户名,前端可用
const me = await ctx.db.get(userId as Id<"users">);
return {
me: { userId, username: me?.name ?? null },
groups: rows,
};
},
});
export const create = mutation({
args: {
id: v.string(),
workspaceId: v.string(),
name: v.string(),
},
handler: async (ctx, args) => {
const userId = await requireUserId(ctx);
await requireWorkspaceMember(ctx, args.workspaceId, userId);
const name = normalizeGroupName(args.name);
const ts = nowIso();
const existing = await ctx.db
.query("groups")
.withIndex("by_group_id", (q: any) => q.eq("id", args.id))
.first();
if (existing) throw new Error("群组已存在");
await ctx.db.insert("groups", {
id: args.id,
workspace_id: args.workspaceId,
name,
created_by: userId,
created_at: ts,
updated_at: ts,
});
await ctx.db.insert("group_members", {
workspace_id: args.workspaceId,
group_id: args.id,
user_id: userId,
role: "owner",
created_at: ts,
});
return { ok: true, id: args.id };
},
});
export const rename = mutation({
args: { groupId: v.string(), name: v.string() },
handler: async (ctx, args) => {
const userId = await requireUserId(ctx);
const group = await ctx.db
.query("groups")
.withIndex("by_group_id", (q: any) => q.eq("id", args.groupId))
.first();
if (!group) throw new Error("群组不存在");
await requireWorkspaceMember(ctx, group.workspace_id, userId);
const membership = await ctx.db
.query("group_members")
.withIndex("by_group_user", (q: any) => q.eq("group_id", args.groupId).eq("user_id", userId))
.first();
if (!membership || membership.role !== "owner") throw new Error("无权限");
const ts = nowIso();
await ctx.db.patch(group._id, { name: normalizeGroupName(args.name), updated_at: ts });
return { ok: true };
},
});
export const remove = mutation({
args: { groupId: v.string() },
handler: async (ctx, args) => {
const userId = await requireUserId(ctx);
const group = await ctx.db
.query("groups")
.withIndex("by_group_id", (q: any) => q.eq("id", args.groupId))
.first();
if (!group) return { ok: true };
await requireWorkspaceMember(ctx, group.workspace_id, userId);
const membership = await ctx.db
.query("group_members")
.withIndex("by_group_user", (q: any) => q.eq("group_id", args.groupId).eq("user_id", userId))
.first();
if (!membership || membership.role !== "owner") throw new Error("无权限");
const members = await ctx.db
.query("group_members")
.withIndex("by_group", (q: any) => q.eq("group_id", args.groupId))
.collect();
const shares = await ctx.db
.query("document_group_shares")
.withIndex("by_workspace_group", (q: any) => q.eq("workspace_id", group.workspace_id).eq("group_id", args.groupId))
.collect();
const perms = await ctx.db
.query("document_group_user_permissions")
.withIndex("by_workspace_group", (q: any) => q.eq("workspace_id", group.workspace_id).eq("group_id", args.groupId))
.collect();
for (const m of members) {
await ctx.db.delete(m._id);
}
for (const s of shares) {
await ctx.db.delete(s._id);
}
for (const p of perms) {
await ctx.db.delete(p._id);
}
await ctx.db.delete(group._id);
return { ok: true };
},
});