Something went wrong. Try again.
opencode v2 mirror
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235import { z } from "zod"import { and, eq, getTableColumns, isNull, sql } from "drizzle-orm"import { fn } from "./util/fn"import { Database } from "./drizzle"import { UserRole, UserTable } from "./schema/user.sql"import { Actor } from "./actor"import { Identifier } from "./identifier"import { render } from "@jsx-email/render"import { AWS } from "./aws"import { Key } from "./key"import { KeyTable } from "./schema/key.sql"import { WorkspaceTable } from "./schema/workspace.sql"import { AuthTable } from "./schema/auth.sql"import { AccountTable } from "./schema/account.sql"
export namespace User { const assertNotSelf = (id: string) => { if (Actor.userID() !== id) return throw new Error(`Expected not self actor, got self actor`) }
export const list = fn(z.void(), () => Database.use((tx) => tx .select({ ...getTableColumns(UserTable), authEmail: AuthTable.subject, }) .from(UserTable) .leftJoin(AuthTable, and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email"))) .where(and(eq(UserTable.workspaceID, Actor.workspace()), isNull(UserTable.timeDeleted))), ), )
export const fromID = fn(z.string(), (id) => Database.use((tx) => tx .select() .from(UserTable) .where(and(eq(UserTable.workspaceID, Actor.workspace()), eq(UserTable.id, id), isNull(UserTable.timeDeleted))) .then((rows) => rows[0]), ), )
export const getAuthEmail = fn(z.string(), (id) => Database.use((tx) => tx .select({ email: AuthTable.subject, }) .from(UserTable) .leftJoin(AuthTable, and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email"))) .where(and(eq(UserTable.workspaceID, Actor.workspace()), eq(UserTable.id, id))) .then((rows) => rows[0]?.email), ), )
export const invite = fn( z.object({ email: z.string(), role: z.enum(UserRole), monthlyLimit: z.number().nullable().optional(), }), async ({ email, role, monthlyLimit }) => { Actor.assertAdmin() const workspaceID = Actor.workspace()
// create user const accountID = await Database.use((tx) => tx .select({ accountID: AuthTable.accountID, }) .from(AuthTable) .where(and(eq(AuthTable.provider, "email"), eq(AuthTable.subject, email))) .then((rows) => rows[0]?.accountID), ) await Database.use((tx) => tx .insert(UserTable) .values({ id: Identifier.create("user"), name: "", ...(accountID ? { accountID, } : { email, }), workspaceID, role, monthlyLimit, }) .onDuplicateKeyUpdate({ set: { role, monthlyLimit, timeDeleted: null, }, }), )
// create api key if (accountID) { await Database.use(async (tx) => { const user = await tx .select() .from(UserTable) .where(and(eq(UserTable.workspaceID, workspaceID), eq(UserTable.accountID, accountID))) .then((rows) => rows[0])
const key = await tx .select() .from(KeyTable) .where(and(eq(KeyTable.workspaceID, workspaceID), eq(KeyTable.userID, user.id))) .then((rows) => rows[0])
if (key) return
await Key.create({ userID: user.id, name: "Default API Key" }) }) }
// send email, ignore errors try { const emailInfo = await Database.use((tx) => tx .select({ inviterEmail: AuthTable.subject, workspaceName: WorkspaceTable.name, }) .from(UserTable) .innerJoin(AuthTable, and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email"))) .innerJoin(WorkspaceTable, eq(WorkspaceTable.id, workspaceID)) .where( and(eq(UserTable.workspaceID, workspaceID), eq(UserTable.id, Actor.assert("user").properties.userID)), ) .then((rows) => rows[0]), )
const { InviteEmail } = await import("@opencode/console-mail/InviteEmail.jsx") await AWS.sendEmail({ to: email, subject: `You've been invited to join the ${emailInfo.workspaceName} workspace on OpenCode`, body: render( // @ts-ignore InviteEmail({ inviter: emailInfo.inviterEmail, assetsUrl: `https://opencode.ai/email`, workspaceID: workspaceID, workspaceName: emailInfo.workspaceName, }), ), }) } catch (e) { console.error(e) } }, )
export const joinInvitedWorkspaces = fn(z.void(), async () => { const account = Actor.assert("account") const invitations = await Database.use(async (tx) => { const active = await tx .select({ id: AccountTable.id }) .from(AccountTable) .where(and(eq(AccountTable.id, account.properties.accountID), isNull(AccountTable.timeDeleted))) .then((rows) => rows[0]) if (!active) throw new Error("Account is not active")
const invitations = await tx .select({ id: UserTable.id, workspaceID: UserTable.workspaceID, }) .from(UserTable) .where(eq(UserTable.email, account.properties.email))
await tx .update(UserTable) .set({ accountID: account.properties.accountID, email: null, }) .where(eq(UserTable.email, account.properties.email)) return invitations })
await Promise.all( invitations.map((invite) => Actor.provide( "system", { workspaceID: invite.workspaceID, }, () => Key.create({ userID: invite.id, name: "Default API Key" }), ), ), ) })
export const update = fn( z.object({ id: z.string(), role: z.enum(UserRole), monthlyLimit: z.number().nullable(), }), async ({ id, role, monthlyLimit }) => { Actor.assertAdmin() if (role === "member") assertNotSelf(id) return await Database.use((tx) => tx .update(UserTable) .set({ role, monthlyLimit }) .where(and(eq(UserTable.id, id), eq(UserTable.workspaceID, Actor.workspace()))), ) }, )
export const remove = fn(z.string(), async (id) => { Actor.assertAdmin() assertNotSelf(id)
return await Database.use((tx) => tx .update(UserTable) .set({ timeDeleted: sql`now()`, }) .where(and(eq(UserTable.id, id), eq(UserTable.workspaceID, Actor.workspace()))), ) })}