173 lines
5.2 KiB
TypeScript
173 lines
5.2 KiB
TypeScript
import { mutation, query } from "./_generated/server";
|
|
import { v } from "convex/values";
|
|
import { requireUserId } from "./_utils/auth";
|
|
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) => {
|
|
const userId = await requireUserId(ctx);
|
|
|
|
const memberships = await ctx.db
|
|
.query("workspace_members")
|
|
.withIndex("by_user_id", (q) => q.eq("user_id", userId))
|
|
.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,
|
|
created_by: userId,
|
|
created_at: ts,
|
|
});
|
|
|
|
await ctx.db.insert("workspace_members", {
|
|
workspace_id: args.workspaceIdIfCreate,
|
|
user_id: userId,
|
|
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({
|
|
args: {},
|
|
handler: async (ctx, args) => {
|
|
const userId = await requireUserId(ctx);
|
|
|
|
// 说明:为了复用 ensureDefaultWorkspace 的返回结构,这里直接走同样的聚合逻辑。
|
|
const memberships = await ctx.db
|
|
.query("workspace_members")
|
|
.withIndex("by_user_id", (q) => q.eq("user_id", userId))
|
|
.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({
|
|
args: { workspaceId: v.string() },
|
|
handler: async (ctx, args) => {
|
|
const userId = await requireUserId(ctx);
|
|
|
|
const target = await ctx.db
|
|
.query("workspace_members")
|
|
.withIndex("by_workspace_user", (q) =>
|
|
q.eq("workspace_id", args.workspaceId).eq("user_id", userId),
|
|
)
|
|
.first();
|
|
|
|
if (!target) {
|
|
throw new Error("无权切换至该工作空间");
|
|
}
|
|
|
|
const memberships = await ctx.db
|
|
.query("workspace_members")
|
|
.withIndex("by_user_id", (q) => q.eq("user_id", userId))
|
|
.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 };
|
|
},
|
|
});
|