206 lines
6.2 KiB
TypeScript
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 };
|
|
},
|
|
});
|