import { db, schema } from "@superlog/db"; import { and, eq, inArray } from "drizzle-orm"; import { HTTPException } from "hono/http-exception"; export type AccessibleProject = { id: string; name: string; slug: string; orgId: string; orgName: string; orgSlug: string; }; export async function listAccessibleProjects(userId: string): Promise { const memberships = await db.query.orgMembers.findMany({ where: eq(schema.orgMembers.userId, userId), }); if (memberships.length === 0) return []; const orgIds = memberships.map((m) => m.orgId); const orgs = await db.query.orgs.findMany({ where: inArray(schema.orgs.id, orgIds), }); const orgById = new Map(orgs.map((o) => [o.id, o])); const projects = await db.query.projects.findMany({ where: inArray(schema.projects.orgId, orgIds), }); return projects .map((p) => { const o = orgById.get(p.orgId); if (!o) return null; return { id: p.id, name: p.name, slug: p.slug, orgId: o.id, orgName: o.name, orgSlug: o.slug, }; }) .filter((x): x is AccessibleProject => x !== null); } export async function assertProjectAccess(userId: string, projectId: string): Promise { const project = await db.query.projects.findFirst({ where: eq(schema.projects.id, projectId), }); if (!project) throw new HTTPException(404, { message: "project not found" }); const membership = await db.query.orgMembers.findFirst({ where: and( eq(schema.orgMembers.userId, userId), eq(schema.orgMembers.orgId, project.orgId), ), }); if (!membership) throw new HTTPException(403, { message: "no access to project" }); } export async function setActiveProjectForToken( tokenId: string, userId: string, projectId: string, ): Promise { await assertProjectAccess(userId, projectId); await db .update(schema.mcpOauthTokens) .set({ projectId }) .where(eq(schema.mcpOauthTokens.id, tokenId)); const projects = await listAccessibleProjects(userId); const found = projects.find((p) => p.id === projectId); if (!found) throw new HTTPException(500, { message: "project vanished after switch" }); return found; }