import type { Doc, Id } from "./_generated/dataModel"; import { mutation, query } from "./_generated/server"; import { requireAuthUserId } from "./authz"; interface OrganizationView { readonly _id: Id<"organizations">; readonly _creationTime: number; readonly name: string; readonly kind: "personal" | "team"; readonly createdBy: string; readonly createdAt: number; } const toView = (org: Doc<"organizations">): OrganizationView => ({ _creationTime: org._creationTime, _id: org._id, createdAt: org.createdAt, createdBy: org.createdBy, kind: org.kind, name: org.name, }); /** * Idempotently create (or return) the authenticated user's single personal * organization plus an owner membership. Convex mutations are atomic * transactions: if two concurrent calls both observe no existing org and both * insert, the transaction's optimistic-concurrency check rejects one; the * retried call re-reads and returns the now-existing org. The read+write is the * whole transaction, so no duplicate personal org can persist. */ export const ensurePersonalOrganization = mutation({ args: {}, handler: async (ctx): Promise => { const ownerId = await requireAuthUserId(ctx); const existing = await ctx.db .query("organizations") .withIndex("by_createdBy_and_kind", (q) => q.eq("createdBy", ownerId).eq("kind", "personal") ) .unique(); if (existing) { return toView(existing); } const createdAt = Date.now(); const organizationId = await ctx.db.insert("organizations", { createdAt, createdBy: ownerId, kind: "personal", name: "Personal", }); await ctx.db.insert("organizationMembers", { createdAt, organizationId, role: "owner", userId: ownerId, }); const created = await ctx.db.get(organizationId); if (!created) { throw new Error("Failed to create personal organization"); } return toView(created); }, }); /** * Return the authenticated user's current organization. In the single personal * organization phase this is the user's one personal org. Returns null when the * user has not yet ensured one. Denies unauthenticated access. */ export const getCurrent = query({ args: {}, handler: async (ctx): Promise => { const ownerId = await requireAuthUserId(ctx); const existing = await ctx.db .query("organizations") .withIndex("by_createdBy_and_kind", (q) => q.eq("createdBy", ownerId).eq("kind", "personal") ) .unique(); return existing ? toView(existing) : null; }, });