Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Billing on Stripe's pages, month-end charges, warnings; sudo by workspace | 1 | /** |
| 2 | * sudo is organised around workspaces, as customers know them. Identity | |
| 3 | * says which workspaces exist and who owns them; billing says who pays for | |
| 4 | * each and where it stands. This joins the two, by slug. No Workers | |
| 5 | * imports, so it can be tested under Node. | |
| 6 | * | |
| 7 | * Billing's account ids (`ws_<slug>`, `ent_…`) are internal: they are used | |
| 8 | * to look things up and never shown as a name. | |
| 9 | */ | |
| 10 | import type { AccountSummary, AdminOwner, AdminWorkspace, Limit, Terms } from "@g1t/contracts"; | |
| 11 | ||
| 12 | /** Terms every workspace starts on. */ | |
| 13 | export const STANDARD_TERMS: Terms = { | |
| 14 | kind: "standard", | |
| 15 | discountPercent: 0, | |
| 16 | ceilingMicros: null, | |
| 17 | note: "", | |
| 18 | until: null, | |
| 19 | setBy: null, | |
| 20 | setAt: null, | |
| 21 | }; | |
| 22 | ||
| 23 | export type Enterprise = { id: string; name: string }; | |
| 24 | ||
| 25 | /** Where a workspace stands with billing. */ | |
| 26 | export type WorkspaceBilling = { | |
| 27 | /** The enterprise that pays for it, or null when it pays for itself. */ | |
| 28 | billedTo: Enterprise | null; | |
| 29 | /** The terms it is charged on: its own, or its enterprise's. */ | |
| 30 | terms: Terms; | |
| 31 | /** | |
| 32 | * Its limit this month: its own, or for a workspace on an enterprise the | |
| 33 | * enterprise's, which all its workspaces share. Null with no billing | |
| 34 | * activity yet. | |
| 35 | */ | |
| 36 | limit: Limit | null; | |
| 37 | /** This workspace's own figures this month (charged, cost) and ever (paid). */ | |
| 38 | chargedMicros: number; | |
| 39 | costMicros: number; | |
| 40 | paidMicros: number; | |
| 41 | }; | |
| 42 | ||
| 43 | export type WorkspaceRow = { | |
| 44 | slug: string; | |
| 45 | name: string; | |
| 46 | /** Null for a workspace billing knows and identity returned nothing for. */ | |
| 47 | createdAt: string | null; | |
| 48 | owners: AdminOwner[]; | |
| 49 | memberCount: number | null; | |
| 50 | /** False when only billing knows the slug. */ | |
| 51 | known: boolean; | |
| 52 | billing: WorkspaceBilling; | |
| 53 | }; | |
| 54 | ||
| 55 | /** The billing side of every workspace billing knows, by slug. */ | |
| 56 | export function billingBySlug(accounts: AccountSummary[]): Map<string, WorkspaceBilling> { | |
| 57 | const bySlug = new Map<string, WorkspaceBilling>(); | |
| 58 | for (const summary of accounts) { | |
| 59 | const { account } = summary; | |
| 60 | const shares = new Map((summary.byWorkspace ?? []).map((share) => [share.workspace, share])); | |
| 61 | if (account.kind === "enterprise") { | |
| 62 | for (const slug of account.workspaces) { | |
| 63 | const share = shares.get(slug); | |
| 64 | bySlug.set(slug, { | |
| 65 | billedTo: { id: account.id, name: account.name }, | |
| 66 | terms: account.terms, | |
| 67 | limit: summary.limit, | |
| 68 | chargedMicros: share?.chargedMicros ?? 0, | |
| 69 | costMicros: share?.costMicros ?? 0, | |
| 70 | paidMicros: share?.paidMicros ?? 0, | |
| 71 | }); | |
| 72 | } | |
| 73 | } else { | |
| 74 | const slug = account.workspaces[0] ?? summary.limit.workspace; | |
| 75 | // An enterprise's claim on a workspace wins over a stale own account. | |
| 76 | if (bySlug.get(slug)?.billedTo) continue; | |
| 77 | bySlug.set(slug, { | |
| 78 | billedTo: null, | |
| 79 | terms: account.terms, | |
| 80 | limit: summary.limit, | |
| 81 | chargedMicros: summary.chargedMicros, | |
| 82 | costMicros: summary.costMicros, | |
| 83 | paidMicros: summary.paidMicros, | |
| 84 | }); | |
| 85 | } | |
| 86 | } | |
| 87 | return bySlug; | |
| 88 | } | |
| 89 | ||
| 90 | /** A workspace with no billing activity: standard terms, nothing charged. */ | |
| 91 | export function noBilling(): WorkspaceBilling { | |
| 92 | return { billedTo: null, terms: STANDARD_TERMS, limit: null, chargedMicros: 0, costMicros: 0, paidMicros: 0 }; | |
| 93 | } | |
| 94 | ||
| 95 | /** | |
| 96 | * Every workspace identity listed, in its order, with its billing; then any | |
| 97 | * workspace only billing knows. A workspace with no billing activity is | |
| 98 | * still listed, on standard terms at $0. | |
| 99 | */ | |
| 100 | export function joinWorkspaces(workspaces: AdminWorkspace[], accounts: AccountSummary[]): WorkspaceRow[] { | |
| 101 | const billing = billingBySlug(accounts); | |
| 102 | const rows: WorkspaceRow[] = workspaces.map((workspace) => ({ | |
| 103 | slug: workspace.slug, | |
| 104 | name: workspace.name, | |
| 105 | createdAt: workspace.createdAt, | |
| 106 | owners: workspace.owners, | |
| 107 | memberCount: workspace.memberCount, | |
| 108 | known: true, | |
| 109 | billing: billing.get(workspace.slug) ?? noBilling(), | |
| 110 | })); | |
| 111 | const listed = new Set(rows.map((row) => row.slug)); | |
| 112 | for (const [slug, entry] of billing) { | |
| 113 | if (listed.has(slug)) continue; | |
| 114 | rows.push({ slug, name: slug, createdAt: null, owners: [], memberCount: null, known: false, billing: entry }); | |
| 115 | } | |
| 116 | return rows; | |
| 117 | } | |
| 118 | ||
| 119 | /** Whether a row matches a search: slug, name, owner, owner's email or enterprise. */ | |
| 120 | export function matchesQuery(row: WorkspaceRow, query: string): boolean { | |
| 121 | const q = query.trim().toLowerCase(); | |
| 122 | if (!q) return true; | |
| 123 | const haystack = [ | |
| 124 | row.slug, | |
| 125 | row.name, | |
| 126 | row.billing.billedTo?.name, | |
| 127 | ...row.owners.flatMap((owner) => [owner.username, owner.email]), | |
| 128 | ]; | |
| 129 | return haystack.some((value) => value?.toLowerCase().includes(q)); | |
| 130 | } | |
| 131 | ||
| 132 | /** | |
| 133 | * Where an old `/accounts/<id>` link now lives: a workspace's own account | |
| 134 | * (`ws_<slug>`, or a bare slug) at its workspace, an enterprise at its own | |
| 135 | * page. Null for anything else. | |
| 136 | */ | |
| 137 | export function legacyAccountPath(id: string): string | null { | |
| 138 | const value = id.trim().toLowerCase(); | |
| 139 | if (/^ent_[a-z0-9_-]{1,80}$/.test(value)) return `/enterprises/${encodeURIComponent(value)}`; | |
| 140 | const slug = value.startsWith("ws_") ? value.slice(3) : value; | |
| 141 | if (/^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,38}$/.test(slug)) return `/workspaces/${encodeURIComponent(slug)}`; | |
| 142 | return null; | |
| 143 | } | |
| 144 | ||
| 145 | /** A row's owners' usernames, as one line. */ | |
| 146 | export function ownerNames(owners: AdminOwner[]): string { | |
| 147 | return owners.map((owner) => owner.username).join(", "); | |
| 148 | } |