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 normalizeUsername(raw: string): string { const username = String(raw ?? "").trim(); if (!username) throw new Error("用户名不能为空"); if (username.length < 2 || username.length > 32) throw new Error("用户名长度需为 2-32 个字符"); if (/\s/.test(username)) throw new Error("用户名不能包含空格"); return username; } async function lookupUserIdByUsername(ctx: any, rawUsername: string): Promise { const username = normalizeUsername(rawUsername); const user = await ctx.db .query("users") .filter((q: any) => q.eq(q.field("name"), username)) .first(); if (!user) throw new Error("未找到该用户"); return String(user._id); } async function requireGroup(ctx: any, groupId: string) { const group = await ctx.db .query("groups") .withIndex("by_group_id", (q: any) => q.eq("id", groupId)) .first(); if (!group) throw new Error("群组不存在"); return group; } async function requireGroupOwner(ctx: any, groupId: string, userId: string) { const membership = await ctx.db .query("group_members") .withIndex("by_group_user", (q: any) => q.eq("group_id", groupId).eq("user_id", userId)) .first(); if (!membership || membership.role !== "owner") throw new Error("无权限"); return membership; } export const listByGroup = query({ args: { groupId: v.string() }, handler: async (ctx, args) => { const userId = await requireUserId(ctx); const group = await requireGroup(ctx, args.groupId); 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) throw new Error("无权限"); const members = await ctx.db .query("group_members") .withIndex("by_group", (q: any) => q.eq("group_id", args.groupId)) .collect(); const result = []; for (const m of members) { const user = await ctx.db.get(m.user_id as Id<"users">); result.push({ userId: m.user_id, username: user?.name ?? null, role: m.role, createdAt: m.created_at, }); } return result; }, }); export const inviteByUsername = mutation({ args: { groupId: v.string(), username: v.string() }, handler: async (ctx, args) => { throw new Error("接口已废弃:请改用 groupInvitations.inviteByUsername(支持对方接受/拒绝)。"); }, }); export const removeMember = mutation({ args: { groupId: v.string(), userId: v.string() }, handler: async (ctx, args) => { const userId = await requireUserId(ctx); const group = await requireGroup(ctx, args.groupId); await requireWorkspaceMember(ctx, group.workspace_id, userId); await requireGroupOwner(ctx, args.groupId, userId); const existing = await ctx.db .query("group_members") .withIndex("by_group_user", (q: any) => q.eq("group_id", args.groupId).eq("user_id", args.userId)) .first(); if (!existing) return { ok: true }; if (existing.role === "owner") throw new Error("不能移除群主"); await ctx.db.delete(existing._id); return { ok: true }; }, });