import type { Database } from "@atbb/db"; import { users, memberships } from "@atbb/db"; import { eq, and } from "drizzle-orm"; import type { SeededRole } from "./seed-roles.js"; interface AssignOwnerResult { assigned: boolean; skipped: boolean; roleUri?: string; } /** * Assign the Owner role to a user via direct DB insert. * * The CLI cannot write to the user's PDS (no user credentials), so * this inserts the membership directly into the database as a bootstrap * shortcut. When the user logs in via OAuth, the normal membership * flow will create the PDS record on their repo. * * Idempotent: skips if the user already has a membership with the * Owner role URI. */ export async function assignOwnerRole( db: Database, forumDid: string, ownerDid: string, ownerHandle: string | undefined, seededRoles: SeededRole[] ): Promise { // Find the Owner role from seeded roles const ownerRole = seededRoles.find((r) => r.name === "Owner"); if (!ownerRole) { throw new Error( "Owner role not found in seeded roles. Run role seeding first." ); } const forumUri = `at://${forumDid}/space.atbb.forum.forum/self`; // Check if user already has a membership with this role const [existingMembership] = await db .select() .from(memberships) .where(and(eq(memberships.did, ownerDid), eq(memberships.roleUri, ownerRole.uri))) .limit(1); if (existingMembership) { return { assigned: false, skipped: true, roleUri: ownerRole.uri }; } // Ensure user exists in the users table (FK constraint) await db .insert(users) .values({ did: ownerDid, handle: ownerHandle ?? null, indexedAt: new Date(), }) .onConflictDoNothing(); // Insert membership directly into DB. // rkey and cid use "bootstrap" sentinel — there is no PDS record backing // this membership yet. When the user first logs in via OAuth, // createMembershipForUser detects cid==="bootstrap" and upgrades the row // by writing a real PDS record and updating rkey/cid. const now = new Date(); await db.insert(memberships).values({ did: ownerDid, rkey: "bootstrap", cid: "bootstrap", forumUri, roleUri: ownerRole.uri, role: "Owner", joinedAt: now, createdAt: now, indexedAt: now, }); return { assigned: true, skipped: false, roleUri: ownerRole.uri }; }