Buckets:
| import { 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" | |
| 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-ai/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 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()))), | |
| ) | |
| }) | |
| } | |
Xet Storage Details
- Size:
- 6.71 kB
- Xet hash:
- 3f8b2f313c8e9756bf243002b54b3174a5f8ff4192de6f15e6a141a20d17eeb9
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.