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

173 lines
5.2 KiB
TypeScript
Raw Normal View History

2026-01-17 10:12:53 +08:00
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
2026-04-14 13:22:29 +08:00
import { requireUserId } from "./_utils/auth";
2026-01-17 10:12:53 +08:00
import { nowIso } from "./_utils/time";
import type { MutationCtx, QueryCtx } from "./_generated/server";
type WorkspaceSummary = {
id: string;
name: string;
type: "personal" | "team";
iconUrl: string | null;
memberCount: number;
isDefault: boolean;
};
async function findWorkspaceById(ctx: QueryCtx | MutationCtx, workspaceId: string) {
return await ctx.db
.query("workspaces")
.withIndex("by_workspace_id", (q) => q.eq("id", workspaceId))
.first();
}
async function countMembers(ctx: QueryCtx | MutationCtx, workspaceId: string): Promise<number> {
const members = await ctx.db
.query("workspace_members")
.withIndex("by_workspace_id", (q) => q.eq("workspace_id", workspaceId))
.collect();
return members.length;
}
export const ensureDefaultWorkspace = mutation({
args: {
fallbackName: v.optional(v.string()),
workspaceIdIfCreate: v.string(),
},
handler: async (ctx, args) => {
2026-01-18 19:01:31 +08:00
const userId = await requireUserId(ctx);
2026-01-17 10:12:53 +08:00
const memberships = await ctx.db
.query("workspace_members")
2026-01-18 19:01:31 +08:00
.withIndex("by_user_id", (q) => q.eq("user_id", userId))
2026-01-17 10:12:53 +08:00
.collect();
if (memberships.length === 0) {
const workspaceName = (args.fallbackName ?? "").trim()
? `${args.fallbackName!.trim()} 的空间`
: "我的空间";
const ts = nowIso();
await ctx.db.insert("workspaces", {
id: args.workspaceIdIfCreate,
name: workspaceName,
type: "personal",
icon_url: null,
2026-01-18 19:01:31 +08:00
created_by: userId,
2026-01-17 10:12:53 +08:00
created_at: ts,
});
await ctx.db.insert("workspace_members", {
workspace_id: args.workspaceIdIfCreate,
2026-01-18 19:01:31 +08:00
user_id: userId,
2026-01-17 10:12:53 +08:00
role: "owner",
is_default: true,
created_at: ts,
});
const summary: WorkspaceSummary = {
id: args.workspaceIdIfCreate,
name: workspaceName,
type: "personal",
iconUrl: null,
memberCount: 1,
isDefault: true,
};
return {
workspaces: [summary],
activeWorkspaceId: args.workspaceIdIfCreate,
};
}
// 有 membership 就认为已有 workspace;再兜底一次补齐 workspace 记录。
const workspaceIds = Array.from(new Set(memberships.map((m) => m.workspace_id)));
const summaries: WorkspaceSummary[] = [];
for (const wid of workspaceIds) {
const ws = await findWorkspaceById(ctx, wid);
if (!ws) continue;
const memberCount = await countMembers(ctx, wid);
const isDefault = memberships.some((m) => m.workspace_id === wid && m.is_default);
summaries.push({
id: ws.id,
name: ws.name,
type: ws.type,
iconUrl: ws.icon_url,
memberCount: memberCount || 1,
isDefault,
});
}
// 说明:保持与原 fetchWorkspaceSummaries 一致:默认 workspace 优先,否则取第一个。
const defaultWs = summaries.find((w) => w.isDefault);
const activeWorkspaceId = defaultWs?.id ?? summaries[0]?.id ?? "";
return { workspaces: summaries, activeWorkspaceId };
},
});
export const fetchWorkspaceSummaries = query({
2026-01-18 19:01:31 +08:00
args: {},
2026-01-17 10:12:53 +08:00
handler: async (ctx, args) => {
2026-01-18 19:01:31 +08:00
const userId = await requireUserId(ctx);
2026-01-17 10:12:53 +08:00
// 说明:为了复用 ensureDefaultWorkspace 的返回结构,这里直接走同样的聚合逻辑。
const memberships = await ctx.db
.query("workspace_members")
2026-01-18 19:01:31 +08:00
.withIndex("by_user_id", (q) => q.eq("user_id", userId))
2026-01-17 10:12:53 +08:00
.collect();
const workspaceIds = Array.from(new Set(memberships.map((m) => m.workspace_id)));
const summaries: WorkspaceSummary[] = [];
for (const wid of workspaceIds) {
const ws = await findWorkspaceById(ctx, wid);
if (!ws) continue;
const memberCount = await countMembers(ctx, wid);
const isDefault = memberships.some((m) => m.workspace_id === wid && m.is_default);
summaries.push({
id: ws.id,
name: ws.name,
type: ws.type,
iconUrl: ws.icon_url,
memberCount: memberCount || 1,
isDefault,
});
}
const defaultWs = summaries.find((w) => w.isDefault);
const activeWorkspaceId = defaultWs?.id ?? summaries[0]?.id ?? "";
return { workspaces: summaries, activeWorkspaceId };
},
});
export const switchDefaultWorkspace = mutation({
2026-01-18 19:01:31 +08:00
args: { workspaceId: v.string() },
2026-01-17 10:12:53 +08:00
handler: async (ctx, args) => {
2026-01-18 19:01:31 +08:00
const userId = await requireUserId(ctx);
2026-01-17 10:12:53 +08:00
const target = await ctx.db
.query("workspace_members")
.withIndex("by_workspace_user", (q) =>
2026-01-18 19:01:31 +08:00
q.eq("workspace_id", args.workspaceId).eq("user_id", userId),
2026-01-17 10:12:53 +08:00
)
.first();
if (!target) {
throw new Error("无权切换至该工作空间");
}
const memberships = await ctx.db
.query("workspace_members")
2026-01-18 19:01:31 +08:00
.withIndex("by_user_id", (q) => q.eq("user_id", userId))
2026-01-17 10:12:53 +08:00
.collect();
// 说明:Convex 暂无批量 update,这里逐条 patch。
for (const m of memberships) {
if (m.is_default) {
await ctx.db.patch(m._id, { is_default: false });
}
}
await ctx.db.patch(target._id, { is_default: true });
return { ok: true };
},
});