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 { 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(); 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 }; }, });