diff options
| author | Frank <[email protected]> | 2026-01-08 19:24:20 -0500 |
|---|---|---|
| committer | Frank <[email protected]> | 2026-01-09 01:32:00 -0500 |
| commit | 52fbd16e08fe59d2d0a7a588dfa565a8ff2d2c95 (patch) | |
| tree | 86d28ed49f348ad779bcd9f22f23da9b5b601a13 /packages/console/core/script | |
| parent | cf97633d7d81c68aa154b82eeb718ba99beb13d8 (diff) | |
| download | opencode-52fbd16e08fe59d2d0a7a588dfa565a8ff2d2c95.tar.gz opencode-52fbd16e08fe59d2d0a7a588dfa565a8ff2d2c95.zip | |
wip: zen
wip: zen
Diffstat (limited to 'packages/console/core/script')
| -rw-r--r-- | packages/console/core/script/lookup-user.ts | 119 | ||||
| -rw-r--r-- | packages/console/core/script/onboard-zen-black.ts | 67 |
2 files changed, 146 insertions, 40 deletions
diff --git a/packages/console/core/script/lookup-user.ts b/packages/console/core/script/lookup-user.ts index 0b8e86440..33a18ebfc 100644 --- a/packages/console/core/script/lookup-user.ts +++ b/packages/console/core/script/lookup-user.ts @@ -1,8 +1,11 @@ -import { Database, eq, sql, inArray } from "../src/drizzle/index.js" +import { Database, and, eq, sql } from "../src/drizzle/index.js" import { AuthTable } from "../src/schema/auth.sql.js" import { UserTable } from "../src/schema/user.sql.js" -import { BillingTable, PaymentTable, UsageTable } from "../src/schema/billing.sql.js" +import { BillingTable, PaymentTable, SubscriptionTable, UsageTable } from "../src/schema/billing.sql.js" import { WorkspaceTable } from "../src/schema/workspace.sql.js" +import { BlackData } from "../src/black.js" +import { centsToMicroCents } from "../src/util/price.js" +import { getWeekBounds } from "../src/util/date.js" // get input from command line const identifier = process.argv[2] @@ -56,6 +59,44 @@ async function printWorkspace(workspaceID: string) { printHeader(`Workspace "${workspace.name}" (${workspace.id})`) + await printTable("Users", (tx) => + tx + .select({ + authEmail: AuthTable.subject, + inviteEmail: UserTable.email, + role: UserTable.role, + timeSeen: UserTable.timeSeen, + monthlyLimit: UserTable.monthlyLimit, + monthlyUsage: UserTable.monthlyUsage, + timeDeleted: UserTable.timeDeleted, + fixedUsage: SubscriptionTable.fixedUsage, + rollingUsage: SubscriptionTable.rollingUsage, + timeFixedUpdated: SubscriptionTable.timeFixedUpdated, + timeRollingUpdated: SubscriptionTable.timeRollingUpdated, + timeSubscriptionCreated: SubscriptionTable.timeCreated, + }) + .from(UserTable) + .leftJoin(AuthTable, and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email"))) + .leftJoin(SubscriptionTable, eq(SubscriptionTable.userID, UserTable.id)) + .where(eq(UserTable.workspaceID, workspace.id)) + .then((rows) => + rows.map((row) => { + const subStatus = getSubscriptionStatus(row) + return { + email: (row.timeDeleted ? "❌ " : "") + (row.authEmail ?? row.inviteEmail), + role: row.role, + timeSeen: formatDate(row.timeSeen), + monthly: formatMonthlyUsage(row.monthlyUsage, row.monthlyLimit), + subscribed: formatDate(row.timeSubscriptionCreated), + subWeekly: subStatus.weekly, + subRolling: subStatus.rolling, + rateLimited: subStatus.rateLimited, + retryIn: subStatus.retryIn, + } + }), + ), + ) + await printTable("Billing", (tx) => tx .select({ @@ -124,6 +165,80 @@ async function printWorkspace(workspaceID: string) { ) } +function formatMicroCents(value: number | null | undefined) { + if (value === null || value === undefined) return null + return `$${(value / 100000000).toFixed(2)}` +} + +function formatDate(value: Date | null | undefined) { + if (!value) return null + return value.toISOString().split("T")[0] +} + +function formatMonthlyUsage(usage: number | null | undefined, limit: number | null | undefined) { + const usageText = formatMicroCents(usage) ?? "$0.00" + if (limit === null || limit === undefined) return `${usageText} / no limit` + return `${usageText} / $${limit.toFixed(2)}` +} + +function formatRetryTime(seconds: number) { + const days = Math.floor(seconds / 86400) + if (days >= 1) return `${days} day${days > 1 ? "s" : ""}` + const hours = Math.floor(seconds / 3600) + const minutes = Math.ceil((seconds % 3600) / 60) + if (hours >= 1) return `${hours}hr ${minutes}min` + return `${minutes}min` +} + +function getSubscriptionStatus(row: { + timeSubscriptionCreated: Date | null + fixedUsage: number | null + rollingUsage: number | null + timeFixedUpdated: Date | null + timeRollingUpdated: Date | null +}) { + if (!row.timeSubscriptionCreated) { + return { weekly: null, rolling: null, rateLimited: null, retryIn: null } + } + + const black = BlackData.get() + const now = new Date() + const week = getWeekBounds(now) + + const fixedLimit = black.fixedLimit ? centsToMicroCents(black.fixedLimit * 100) : null + const rollingLimit = black.rollingLimit ? centsToMicroCents(black.rollingLimit * 100) : null + const rollingWindowMs = (black.rollingWindow ?? 5) * 3600 * 1000 + + // Calculate current weekly usage (reset if outside current week) + const currentWeekly = + row.fixedUsage && row.timeFixedUpdated && row.timeFixedUpdated >= week.start ? row.fixedUsage : 0 + + // Calculate current rolling usage + const windowStart = new Date(now.getTime() - rollingWindowMs) + const currentRolling = + row.rollingUsage && row.timeRollingUpdated && row.timeRollingUpdated >= windowStart ? row.rollingUsage : 0 + + // Check rate limiting + const isWeeklyLimited = fixedLimit !== null && currentWeekly >= fixedLimit + const isRollingLimited = rollingLimit !== null && currentRolling >= rollingLimit + + let retryIn: string | null = null + if (isWeeklyLimited) { + const retryAfter = Math.ceil((week.end.getTime() - now.getTime()) / 1000) + retryIn = formatRetryTime(retryAfter) + } else if (isRollingLimited && row.timeRollingUpdated) { + const retryAfter = Math.ceil((row.timeRollingUpdated.getTime() + rollingWindowMs - now.getTime()) / 1000) + retryIn = formatRetryTime(retryAfter) + } + + return { + weekly: fixedLimit !== null ? `${formatMicroCents(currentWeekly)} / $${black.fixedLimit}` : null, + rolling: rollingLimit !== null ? `${formatMicroCents(currentRolling)} / $${black.rollingLimit}` : null, + rateLimited: isWeeklyLimited || isRollingLimited ? "yes" : "no", + retryIn, + } +} + function printHeader(title: string) { console.log() console.log("─".repeat(title.length)) diff --git a/packages/console/core/script/onboard-zen-black.ts b/packages/console/core/script/onboard-zen-black.ts index 15418af2f..06e5afe12 100644 --- a/packages/console/core/script/onboard-zen-black.ts +++ b/packages/console/core/script/onboard-zen-black.ts @@ -1,35 +1,35 @@ import { Billing } from "../src/billing.js" -import { Database, eq, and, sql } from "../src/drizzle/index.js" -import { AuthTable } from "../src/schema/auth.sql.js" +import { and, Database, eq, isNull, sql } from "../src/drizzle/index.js" import { UserTable } from "../src/schema/user.sql.js" -import { BillingTable, PaymentTable } from "../src/schema/billing.sql.js" +import { BillingTable, PaymentTable, SubscriptionTable } from "../src/schema/billing.sql.js" import { Identifier } from "../src/identifier.js" import { centsToMicroCents } from "../src/util/price.js" +import { AuthTable } from "../src/schema/auth.sql.js" const workspaceID = process.argv[2] const email = process.argv[3] +console.log(`Onboarding workspace ${workspaceID} for email ${email}`) + if (!workspaceID || !email) { console.error("Usage: bun onboard-zen-black.ts <workspaceID> <email>") process.exit(1) } // Look up the Stripe customer by email -const customers = await Billing.stripe().customers.list({ email, limit: 1 }) -const customer = customers.data[0] -if (!customer) { +const customers = await Billing.stripe().customers.list({ email, limit: 10, expand: ["data.subscriptions"] }) +if (!customers.data) { console.error(`Error: No Stripe customer found for email ${email}`) process.exit(1) } -const customerID = customer.id - -// Get the subscription id -const subscriptions = await Billing.stripe().subscriptions.list({ customer: customerID, limit: 1 }) -const subscription = subscriptions.data[0] -if (!subscription) { - console.error(`Error: Customer ${customerID} does not have a subscription`) +const customer = customers.data.find((c) => c.subscriptions?.data[0]?.items.data[0]?.price.unit_amount === 20000) +if (!customer) { + console.error(`Error: No Stripe customer found for email ${email} with $200 subscription`) process.exit(1) } + +const customerID = customer.id +const subscription = customer.subscriptions!.data[0] const subscriptionID = subscription.id // Validate the subscription is $200 @@ -90,29 +90,21 @@ const paymentMethod = paymentMethodID ? await Billing.stripe().paymentMethods.re const paymentMethodLast4 = paymentMethod?.card?.last4 ?? null const paymentMethodType = paymentMethod?.type ?? null -// Look up the user by email via AuthTable -const auth = 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]), -) -if (!auth) { - console.error(`Error: No user found with email ${email}`) - process.exit(1) -} - // Look up the user in the workspace -const user = await Database.use((tx) => +const users = await Database.use((tx) => tx - .select({ id: UserTable.id }) + .select({ id: UserTable.id, email: AuthTable.subject }) .from(UserTable) - .where(and(eq(UserTable.workspaceID, workspaceID), eq(UserTable.accountID, auth.accountID))) - .then((rows) => rows[0]), + .innerJoin(AuthTable, and(eq(AuthTable.accountID, UserTable.accountID), eq(AuthTable.provider, "email"))) + .where(and(eq(UserTable.workspaceID, workspaceID), isNull(UserTable.timeDeleted))), ) +if (users.length === 0) { + console.error(`Error: No users found in workspace ${workspaceID}`) + process.exit(1) +} +const user = users.length === 1 ? users[0] : users.find((u) => u.email === email) if (!user) { - console.error(`Error: User with email ${email} is not a member of workspace ${workspaceID}`) + console.error(`Error: User with email ${email} not found in workspace ${workspaceID}`) process.exit(1) } @@ -136,13 +128,12 @@ await Database.transaction(async (tx) => { }) .where(eq(BillingTable.workspaceID, workspaceID)) - // Set current time as timeSubscribed on user - await tx - .update(UserTable) - .set({ - timeSubscribed: sql`now()`, - }) - .where(eq(UserTable.id, user.id)) + // Create a row in subscription table + await tx.insert(SubscriptionTable).values({ + workspaceID, + id: Identifier.create("subscription"), + userID: user.id, + }) // Create a row in payments table await tx.insert(PaymentTable).values({ |
