tapimo 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/App.d.ts +8641 -8263
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +9 -1
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +4 -0
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +5 -0
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +112 -11
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +2 -1
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/early-access.d.ts +2 -2
- package/dist/admin/apps/early-access.d.ts.map +1 -1
- package/dist/admin/apps/early-access.js +5 -5
- package/dist/admin/apps/early-access.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +124 -1
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/organizations.js +97 -6
- package/dist/admin/apps/organizations.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +95 -87
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +20 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +18 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +36 -7
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +5016 -4985
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.js +2 -15
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +1 -1
- package/dist/apps/data/FxOracle.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.js +1 -1
- package/dist/apps/data/FxOracle.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +27 -27
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +21 -1
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +58 -58
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/coingecko.d.ts +176 -176
- package/dist/apps/data/routes/exchanges.d.ts +113 -113
- package/dist/apps/data/routes/fee-amm.d.ts +105 -91
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +76 -91
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/funding.d.ts +72 -34
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +34 -26
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +5 -5
- package/dist/apps/data/routes/receipts.d.ts +6 -6
- package/dist/apps/data/routes/rpc.d.ts +31 -31
- package/dist/apps/data/routes/tokens.d.ts +2 -2
- package/dist/apps/data/routes/tokens.js +1 -1
- package/dist/apps/data/routes/transactions.d.ts +4 -4
- package/dist/apps/data/routes/transfers.d.ts +99 -88
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +226 -141
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +34 -34
- package/dist/apps/data/routes/valuation.js +3 -3
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/data/routes/webhooks.d.ts +366 -366
- package/dist/apps/index.d.ts +1 -0
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -0
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +460 -523
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +7 -23
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +20 -8
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +106 -151
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +28 -28
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +40 -26
- package/dist/apps/management/routes/faucet.d.ts.map +1 -1
- package/dist/apps/management/routes/faucet.js +33 -18
- package/dist/apps/management/routes/faucet.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +131 -131
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +0 -3
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +6 -8
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +0 -4
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +45 -45
- package/dist/apps/management/routes/orgs.d.ts +94 -94
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +5 -0
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +32 -32
- package/dist/apps/management/routes/verified-token-requests.d.ts +94 -94
- package/dist/apps/management/routes/webhooks.d.ts +1 -1
- package/dist/apps/mpp/App.d.ts +363 -0
- package/dist/apps/mpp/App.d.ts.map +1 -0
- package/dist/apps/mpp/App.js +72 -0
- package/dist/apps/mpp/App.js.map +1 -0
- package/dist/apps/mpp/Mpp.d.ts +178 -0
- package/dist/apps/mpp/Mpp.d.ts.map +1 -0
- package/dist/apps/mpp/Mpp.js +183 -0
- package/dist/apps/mpp/Mpp.js.map +1 -0
- package/dist/apps/mpp/index.d.ts +3 -0
- package/dist/apps/mpp/index.d.ts.map +1 -0
- package/dist/apps/mpp/index.js +3 -0
- package/dist/apps/mpp/index.js.map +1 -0
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +56 -9
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/db/Db.d.ts +2 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +27 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +12 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +11 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/enabledBillingSources.d.ts +73 -0
- package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
- package/dist/db/tables/enabledBillingSources.js +69 -0
- package/dist/db/tables/enabledBillingSources.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +1 -12
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/invitations.js +3 -23
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +5 -2
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +13 -2
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/handlers/internal/utils.d.ts +1 -16
- package/dist/handlers/internal/utils.d.ts.map +1 -1
- package/dist/handlers/internal/utils.js +17 -6
- package/dist/handlers/internal/utils.js.map +1 -1
- package/dist/handlers/relay.d.ts.map +1 -1
- package/dist/handlers/relay.js +37 -5
- package/dist/handlers/relay.js.map +1 -1
- package/dist/internal/Auth.d.ts +0 -32
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +3 -51
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/FundingQuotes.d.ts +78 -32
- package/dist/internal/FundingQuotes.d.ts.map +1 -1
- package/dist/internal/FundingQuotes.js +45 -8
- package/dist/internal/FundingQuotes.js.map +1 -1
- package/dist/internal/Log.d.ts +18 -4
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +50 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +15 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Response.d.ts +2 -2
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Response.js +12 -4
- package/dist/internal/Response.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/package.json +7 -2
- package/src/App.test.ts +9 -4
- package/src/App.ts +14 -2
- package/src/Client.test-d.ts +109 -28
- package/src/Scope.ts +5 -0
- package/src/admin/App.test.ts +4 -0
- package/src/admin/apps/early-access.ts +6 -5
- package/src/admin/apps/organizations.test.ts +86 -2
- package/src/admin/apps/organizations.ts +155 -31
- package/src/admin/apps/scopes.test.ts +5 -0
- package/src/admin/ui/src/lib/organizations.test.ts +81 -1
- package/src/admin/ui/src/lib/organizations.ts +92 -0
- package/src/admin/ui/src/routes/api-keys.tsx +1 -1
- package/src/admin/ui/src/routes/early-access.tsx +8 -3
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
- package/src/admin/ui.generated.ts +95 -109
- package/src/analytics/Analytics.test.ts +83 -0
- package/src/analytics/Analytics.ts +20 -0
- package/src/analytics/tables/requestEvents.ts +60 -7
- package/src/apps/composition.test.ts +11 -2
- package/src/apps/data/App.test.ts +1 -1
- package/src/apps/data/FundingQuoteProviders.test.ts +0 -5
- package/src/apps/data/FundingQuoteProviders.ts +2 -29
- package/src/apps/data/FxOracle.test.ts +2 -2
- package/src/apps/data/FxOracle.ts +2 -2
- package/src/apps/data/routes/activities.test.ts +6 -2
- package/src/apps/data/routes/activities.ts +39 -1
- package/src/apps/data/routes/balances.test.ts +6 -2
- package/src/apps/data/routes/exchanges.test.ts +7 -3
- package/src/apps/data/routes/fee-amm.test.ts +47 -23
- package/src/apps/data/routes/fee-amm.ts +98 -119
- package/src/apps/data/routes/funding.test.ts +29 -8
- package/src/apps/data/routes/funding.ts +42 -48
- package/src/apps/data/routes/receipts.test.ts +6 -2
- package/src/apps/data/routes/tokens.ts +1 -1
- package/src/apps/data/routes/transfers.test.ts +203 -35
- package/src/apps/data/routes/transfers.ts +294 -172
- package/src/apps/data/routes/valuation.test.ts +18 -3
- package/src/apps/data/routes/valuation.ts +4 -4
- package/src/apps/index.ts +1 -0
- package/src/apps/management/routes/api-keys.test.ts +2 -75
- package/src/apps/management/routes/api-keys.ts +30 -7
- package/src/apps/management/routes/billing.test.ts +106 -85
- package/src/apps/management/routes/billing.ts +32 -21
- package/src/apps/management/routes/faucet.test.ts +11 -3
- package/src/apps/management/routes/faucet.ts +46 -30
- package/src/apps/management/routes/invitations.test.ts +0 -172
- package/src/apps/management/routes/invitations.ts +0 -3
- package/src/apps/management/routes/me.test.ts +3 -145
- package/src/apps/management/routes/me.ts +0 -6
- package/src/apps/management/routes/orgs.test.ts +45 -2
- package/src/apps/management/routes/orgs.ts +6 -0
- package/src/apps/management/routes/scopes.test.ts +1 -0
- package/src/apps/mpp/App.ts +86 -0
- package/src/apps/mpp/Mpp.test.ts +193 -0
- package/src/apps/mpp/Mpp.ts +353 -0
- package/src/apps/mpp/index.ts +2 -0
- package/src/apps/mpp/internal/Adapter.ts +11 -0
- package/src/apps/mpp/internal/Error.ts +9 -0
- package/src/apps/mpp/internal/Idempotency.ts +7 -0
- package/src/apps/mpp/internal/Screening.ts +10 -0
- package/src/apps/relay/App.test.ts +88 -6
- package/src/apps/relay/App.ts +61 -9
- package/src/db/Db.test.ts +2 -2
- package/src/db/Db.ts +30 -0
- package/src/db/Schema.ts +15 -0
- package/src/db/tables/enabledBillingSources.test.ts +303 -0
- package/src/db/tables/enabledBillingSources.ts +118 -0
- package/src/db/tables/invitations.ts +4 -26
- package/src/db/tables/organizations.test.ts +103 -0
- package/src/db/tables/organizations.ts +18 -2
- package/src/handlers/internal/utils.ts +22 -6
- package/src/handlers/relay.errors.test.ts +153 -0
- package/src/handlers/relay.ts +42 -7
- package/src/internal/Auth.test.ts +3 -3
- package/src/internal/Auth.ts +3 -63
- package/src/internal/FundingQuotes.test.ts +18 -5
- package/src/internal/FundingQuotes.ts +46 -11
- package/src/internal/Log.test.ts +91 -0
- package/src/internal/Log.ts +69 -5
- package/src/internal/MetricSink.test.ts +55 -0
- package/src/internal/MetricSink.ts +14 -0
- package/src/internal/Response.test.ts +28 -0
- package/src/internal/Response.ts +12 -4
package/src/db/Db.ts
CHANGED
|
@@ -13,6 +13,7 @@ import * as pg from 'pg'
|
|
|
13
13
|
import type * as AdminAuditLogs from './tables/adminAuditLogs.js'
|
|
14
14
|
import type * as BillingSettings from './tables/billingSettings.js'
|
|
15
15
|
import type * as EarlyAccess from './tables/earlyAccess.js'
|
|
16
|
+
import type * as EnabledBillingSources from './tables/enabledBillingSources.js'
|
|
16
17
|
import type * as Invitations from './tables/invitations.js'
|
|
17
18
|
import type * as InviteLinks from './tables/inviteLinks.js'
|
|
18
19
|
import type * as Memberships from './tables/memberships.js'
|
|
@@ -35,6 +36,7 @@ export type Database = {
|
|
|
35
36
|
admin_audit_logs: AdminAuditLogs.Table
|
|
36
37
|
billing_settings: BillingSettings.Table
|
|
37
38
|
early_access: EarlyAccess.Table
|
|
39
|
+
enabled_billing_sources: EnabledBillingSources.Table
|
|
38
40
|
invitations: Invitations.Table
|
|
39
41
|
invite_link_redemptions: InviteLinks.RedemptionTable
|
|
40
42
|
invite_links: InviteLinks.Table
|
|
@@ -740,6 +742,34 @@ export const migrations: readonly Migration[] = [
|
|
|
740
742
|
name: '0069_invite_links_allowed_email_domains',
|
|
741
743
|
up: (db) => raw(db, sql`ALTER TABLE invite_links ADD COLUMN IF NOT EXISTS allowed_email_domains jsonb`), // prettier-ignore
|
|
742
744
|
},
|
|
745
|
+
{
|
|
746
|
+
name: '0070_enabled_billing_sources',
|
|
747
|
+
up: (db) =>
|
|
748
|
+
db.schema
|
|
749
|
+
.createTable('enabledBillingSources')
|
|
750
|
+
.ifNotExists()
|
|
751
|
+
.addColumn('createdAt', 'text', (c) => c.notNull())
|
|
752
|
+
.addColumn('createdBy', 'text', (c) => c.notNull())
|
|
753
|
+
.addColumn('orgId', 'text', (c) => c.notNull())
|
|
754
|
+
.addColumn('source', 'text', (c) => c.notNull())
|
|
755
|
+
.addPrimaryKeyConstraint('enabled_billing_sources_pkey', ['orgId', 'source']),
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
name: '0071_enabled_billing_sources_stripe_backfill',
|
|
759
|
+
up: (db) => raw(db, sql`INSERT INTO enabled_billing_sources (created_at, created_by, org_id, source) SELECT min(created_at), 'migration', org_id, 'stripe' FROM stripe_customers GROUP BY org_id ON CONFLICT DO NOTHING`), // prettier-ignore
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
name: '0072_enabled_billing_sources_stripe_catch_up',
|
|
763
|
+
up: (db) => raw(db, sql`INSERT INTO enabled_billing_sources (created_at, created_by, org_id, source) SELECT min(created_at), 'migration', org_id, 'stripe' FROM stripe_customers GROUP BY org_id ON CONFLICT DO NOTHING`), // prettier-ignore
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
name: '0073_enabled_billing_sources_early_access_backfill',
|
|
767
|
+
up: (db) => raw(db, sql`INSERT INTO enabled_billing_sources (created_at, created_by, org_id, source) SELECT organizations.created_at, 'migration', organizations.id, 'stripe' FROM organizations INNER JOIN users ON users.id = organizations.user_id WHERE users.email IS NOT NULL AND EXISTS (SELECT 1 FROM early_access WHERE early_access.entry IN (lower(btrim(users.email)), regexp_replace(lower(btrim(users.email)), '^.*@', ''))) ON CONFLICT DO NOTHING`), // prettier-ignore
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
name: '0074_invitations_early_access_default',
|
|
771
|
+
up: (db) => raw(db, sql`ALTER TABLE invitations ALTER COLUMN grants_early_access SET DEFAULT false`), // prettier-ignore
|
|
772
|
+
},
|
|
743
773
|
]
|
|
744
774
|
|
|
745
775
|
/**
|
package/src/db/Schema.ts
CHANGED
|
@@ -66,6 +66,21 @@ export const EarlyAccessEntry = z.object({
|
|
|
66
66
|
/** Columns of the `early_access` table. */
|
|
67
67
|
export type EarlyAccessEntry = z.output<typeof EarlyAccessEntry>
|
|
68
68
|
|
|
69
|
+
/** One billing-source type enabled for an organization. */
|
|
70
|
+
export const EnabledBillingSource = z.object({
|
|
71
|
+
createdAt: z.string().check(z.describe('When the billing source was enabled (ISO 8601).')),
|
|
72
|
+
createdBy: z
|
|
73
|
+
.string()
|
|
74
|
+
.check(z.describe('Identity that enabled the source, or `migration` for backfilled rows.')),
|
|
75
|
+
orgId: z.string().check(z.describe('Organization id (`org_…`) this source is enabled for.')),
|
|
76
|
+
source: z
|
|
77
|
+
.enum(['stripe', 'tempo'])
|
|
78
|
+
.check(z.describe('Enabled billing-source type; `stripe` or `tempo`.')),
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
/** Columns of the `enabled_billing_sources` table. */
|
|
82
|
+
export type EnabledBillingSource = z.output<typeof EnabledBillingSource>
|
|
83
|
+
|
|
69
84
|
/** A pending, expiring offer of organization membership. */
|
|
70
85
|
export const Invitation = z.object({
|
|
71
86
|
acceptedAt: z
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid'
|
|
2
|
+
|
|
3
|
+
import * as Runtime from '../../../test/runtime.js'
|
|
4
|
+
import * as Db from '../Db.js'
|
|
5
|
+
import * as EarlyAccess from './earlyAccess.js'
|
|
6
|
+
import * as EnabledBillingSources from './enabledBillingSources.js'
|
|
7
|
+
import * as Invitations from './invitations.js'
|
|
8
|
+
import * as Organizations from './organizations.js'
|
|
9
|
+
import * as StripeCustomers from './stripeCustomers.js'
|
|
10
|
+
import * as Users from './users.js'
|
|
11
|
+
|
|
12
|
+
const create = () => Db.postgres({ connectionString: Runtime.postgresUrl, schema: `t_${nanoid()}` })
|
|
13
|
+
|
|
14
|
+
async function createOwned(db: Db.Db, options: createOwned.Options) {
|
|
15
|
+
const user = await Users.upsertByAddress(db, { address: options.address })
|
|
16
|
+
if (options.email) await Users.setEmail(db, user.id, options.email)
|
|
17
|
+
const organization = await Organizations.createOwned(db, { name: options.name, userId: user.id })
|
|
18
|
+
return { organization, user }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
declare namespace createOwned {
|
|
22
|
+
/** Inputs for an owned-organization fixture. */
|
|
23
|
+
type Options = {
|
|
24
|
+
/** User address. */
|
|
25
|
+
address: string
|
|
26
|
+
/** Optional user email. */
|
|
27
|
+
email?: string | undefined
|
|
28
|
+
/** Organization name. */
|
|
29
|
+
name: string
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Redacts nondeterministic row fields. */
|
|
34
|
+
function redact(record: EnabledBillingSources.Record) {
|
|
35
|
+
return { ...record, createdAt: '<timestamp>' }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('add', () => {
|
|
39
|
+
test('behavior: enables each source once per organization', async () => {
|
|
40
|
+
const db = create()
|
|
41
|
+
await db.migrate()
|
|
42
|
+
const stripe = await EnabledBillingSources.add(db, {
|
|
43
|
+
createdBy: 'admin@tempo.xyz',
|
|
44
|
+
orgId: 'org_1',
|
|
45
|
+
source: 'stripe',
|
|
46
|
+
})
|
|
47
|
+
expect(stripe && redact(stripe)).toMatchInlineSnapshot(`
|
|
48
|
+
{
|
|
49
|
+
"createdAt": "<timestamp>",
|
|
50
|
+
"createdBy": "admin@tempo.xyz",
|
|
51
|
+
"orgId": "org_1",
|
|
52
|
+
"source": "stripe",
|
|
53
|
+
}
|
|
54
|
+
`)
|
|
55
|
+
expect(
|
|
56
|
+
await EnabledBillingSources.add(db, {
|
|
57
|
+
createdBy: 'other@tempo.xyz',
|
|
58
|
+
orgId: 'org_1',
|
|
59
|
+
source: 'stripe',
|
|
60
|
+
}),
|
|
61
|
+
).toBeUndefined()
|
|
62
|
+
expect(
|
|
63
|
+
await EnabledBillingSources.add(db, {
|
|
64
|
+
createdBy: 'admin@tempo.xyz',
|
|
65
|
+
orgId: 'org_1',
|
|
66
|
+
source: 'tempo',
|
|
67
|
+
}),
|
|
68
|
+
).toBeDefined()
|
|
69
|
+
await db.close()
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
describe('listByOrg', () => {
|
|
74
|
+
test('behavior: lists only the organization in canonical source order', async () => {
|
|
75
|
+
const db = create()
|
|
76
|
+
await db.migrate()
|
|
77
|
+
await EnabledBillingSources.add(db, {
|
|
78
|
+
createdBy: 'admin@tempo.xyz',
|
|
79
|
+
orgId: 'org_1',
|
|
80
|
+
source: 'tempo',
|
|
81
|
+
})
|
|
82
|
+
await EnabledBillingSources.add(db, {
|
|
83
|
+
createdBy: 'admin@tempo.xyz',
|
|
84
|
+
orgId: 'org_2',
|
|
85
|
+
source: 'stripe',
|
|
86
|
+
})
|
|
87
|
+
await EnabledBillingSources.add(db, {
|
|
88
|
+
createdBy: 'admin@tempo.xyz',
|
|
89
|
+
orgId: 'org_1',
|
|
90
|
+
source: 'stripe',
|
|
91
|
+
})
|
|
92
|
+
expect((await EnabledBillingSources.listByOrg(db, 'org_1')).map(redact)).toMatchInlineSnapshot(`
|
|
93
|
+
[
|
|
94
|
+
{
|
|
95
|
+
"createdAt": "<timestamp>",
|
|
96
|
+
"createdBy": "admin@tempo.xyz",
|
|
97
|
+
"orgId": "org_1",
|
|
98
|
+
"source": "stripe",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"createdAt": "<timestamp>",
|
|
102
|
+
"createdBy": "admin@tempo.xyz",
|
|
103
|
+
"orgId": "org_1",
|
|
104
|
+
"source": "tempo",
|
|
105
|
+
},
|
|
106
|
+
]
|
|
107
|
+
`)
|
|
108
|
+
await db.close()
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('behavior: new organizations start with no enabled sources', async () => {
|
|
112
|
+
const db = create()
|
|
113
|
+
await db.migrate()
|
|
114
|
+
await Organizations.create(db, { id: 'org_new', name: 'New' })
|
|
115
|
+
expect(await EnabledBillingSources.listByOrg(db, 'org_new')).toEqual([])
|
|
116
|
+
await db.close()
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
describe('remove', () => {
|
|
121
|
+
test('behavior: removes one source and reports absence', async () => {
|
|
122
|
+
const db = create()
|
|
123
|
+
await db.migrate()
|
|
124
|
+
await EnabledBillingSources.add(db, {
|
|
125
|
+
createdBy: 'admin@tempo.xyz',
|
|
126
|
+
orgId: 'org_1',
|
|
127
|
+
source: 'stripe',
|
|
128
|
+
})
|
|
129
|
+
await EnabledBillingSources.add(db, {
|
|
130
|
+
createdBy: 'admin@tempo.xyz',
|
|
131
|
+
orgId: 'org_1',
|
|
132
|
+
source: 'tempo',
|
|
133
|
+
})
|
|
134
|
+
expect(
|
|
135
|
+
redact(
|
|
136
|
+
(await EnabledBillingSources.remove(db, {
|
|
137
|
+
orgId: 'org_1',
|
|
138
|
+
source: 'stripe',
|
|
139
|
+
}))!,
|
|
140
|
+
),
|
|
141
|
+
).toMatchInlineSnapshot(`
|
|
142
|
+
{
|
|
143
|
+
"createdAt": "<timestamp>",
|
|
144
|
+
"createdBy": "admin@tempo.xyz",
|
|
145
|
+
"orgId": "org_1",
|
|
146
|
+
"source": "stripe",
|
|
147
|
+
}
|
|
148
|
+
`)
|
|
149
|
+
expect(
|
|
150
|
+
await EnabledBillingSources.remove(db, { orgId: 'org_1', source: 'stripe' }),
|
|
151
|
+
).toBeUndefined()
|
|
152
|
+
expect(await EnabledBillingSources.isEnabled(db, { orgId: 'org_1', source: 'tempo' })).toBe(
|
|
153
|
+
true,
|
|
154
|
+
)
|
|
155
|
+
await db.close()
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
describe('isEnabled', () => {
|
|
160
|
+
test('behavior: checks one organization and source', async () => {
|
|
161
|
+
const db = create()
|
|
162
|
+
await db.migrate()
|
|
163
|
+
await EnabledBillingSources.add(db, {
|
|
164
|
+
createdBy: 'admin@tempo.xyz',
|
|
165
|
+
orgId: 'org_1',
|
|
166
|
+
source: 'stripe',
|
|
167
|
+
})
|
|
168
|
+
expect(await EnabledBillingSources.isEnabled(db, { orgId: 'org_1', source: 'stripe' })).toBe(
|
|
169
|
+
true,
|
|
170
|
+
)
|
|
171
|
+
expect(await EnabledBillingSources.isEnabled(db, { orgId: 'org_1', source: 'tempo' })).toBe(
|
|
172
|
+
false,
|
|
173
|
+
)
|
|
174
|
+
expect(await EnabledBillingSources.isEnabled(db, { orgId: 'org_2', source: 'stripe' })).toBe(
|
|
175
|
+
false,
|
|
176
|
+
)
|
|
177
|
+
await db.close()
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
describe('migration', () => {
|
|
182
|
+
test('behavior: catch-up backfills one Stripe source per distinct customer organization', async () => {
|
|
183
|
+
const db = create()
|
|
184
|
+
await db.migrate()
|
|
185
|
+
await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_live_1' })
|
|
186
|
+
await StripeCustomers.create(db, {
|
|
187
|
+
environment: 'sandbox',
|
|
188
|
+
orgId: 'org_1',
|
|
189
|
+
stripeCustomerId: 'cus_test_1',
|
|
190
|
+
})
|
|
191
|
+
await StripeCustomers.create(db, { orgId: 'org_2', stripeCustomerId: 'cus_live_2' })
|
|
192
|
+
await db.kysely
|
|
193
|
+
.deleteFrom('migrations')
|
|
194
|
+
.where('name', '=', '0072_enabled_billing_sources_stripe_catch_up')
|
|
195
|
+
.execute()
|
|
196
|
+
await db.migrate()
|
|
197
|
+
await db.kysely
|
|
198
|
+
.deleteFrom('migrations')
|
|
199
|
+
.where('name', '=', '0072_enabled_billing_sources_stripe_catch_up')
|
|
200
|
+
.execute()
|
|
201
|
+
await db.migrate()
|
|
202
|
+
|
|
203
|
+
expect(
|
|
204
|
+
(await EnabledBillingSources.listByOrg(db, 'org_1')).map((record) => ({
|
|
205
|
+
createdBy: record.createdBy,
|
|
206
|
+
source: record.source,
|
|
207
|
+
})),
|
|
208
|
+
).toMatchInlineSnapshot(`
|
|
209
|
+
[
|
|
210
|
+
{
|
|
211
|
+
"createdBy": "migration",
|
|
212
|
+
"source": "stripe",
|
|
213
|
+
},
|
|
214
|
+
]
|
|
215
|
+
`)
|
|
216
|
+
expect((await EnabledBillingSources.listByOrg(db, 'org_2')).map((record) => record.source))
|
|
217
|
+
.toMatchInlineSnapshot(`
|
|
218
|
+
[
|
|
219
|
+
"stripe",
|
|
220
|
+
]
|
|
221
|
+
`)
|
|
222
|
+
await db.close()
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test('behavior: backfills Stripe only for organizations owned by allowlisted users', async () => {
|
|
226
|
+
const db = create()
|
|
227
|
+
await db.migrate()
|
|
228
|
+
await EarlyAccess.add(db, { createdBy: 'admin@tempo.xyz', entry: 'exact@example.net' })
|
|
229
|
+
await EarlyAccess.add(db, { createdBy: 'admin@tempo.xyz', entry: 'example.org' })
|
|
230
|
+
const exact = await createOwned(db, {
|
|
231
|
+
address: `0x${'11'.repeat(20)}`,
|
|
232
|
+
email: ' Exact@Example.NET ',
|
|
233
|
+
name: 'Exact',
|
|
234
|
+
})
|
|
235
|
+
const domain = await createOwned(db, {
|
|
236
|
+
address: `0x${'22'.repeat(20)}`,
|
|
237
|
+
email: 'Owner@Example.ORG',
|
|
238
|
+
name: 'Domain',
|
|
239
|
+
})
|
|
240
|
+
const invited = await createOwned(db, {
|
|
241
|
+
address: `0x${'33'.repeat(20)}`,
|
|
242
|
+
email: 'Invited@Example.NET',
|
|
243
|
+
name: 'Invited',
|
|
244
|
+
})
|
|
245
|
+
const legacyInvitation = await Invitations.create(db, {
|
|
246
|
+
email: 'invited@example.net',
|
|
247
|
+
expiresAt: '2020-01-01T00:00:00.000Z',
|
|
248
|
+
invitedBy: exact.user.id,
|
|
249
|
+
orgId: exact.organization.id,
|
|
250
|
+
role: 'member',
|
|
251
|
+
})
|
|
252
|
+
await db.kysely
|
|
253
|
+
.updateTable('invitations')
|
|
254
|
+
.set({ grantsEarlyAccess: true })
|
|
255
|
+
.where('id', '=', legacyInvitation.id)
|
|
256
|
+
.execute()
|
|
257
|
+
const plain = await createOwned(db, {
|
|
258
|
+
address: `0x${'44'.repeat(20)}`,
|
|
259
|
+
email: 'plain@example.net',
|
|
260
|
+
name: 'Plain',
|
|
261
|
+
})
|
|
262
|
+
const emailLess = await createOwned(db, {
|
|
263
|
+
address: `0x${'55'.repeat(20)}`,
|
|
264
|
+
name: 'Email-less',
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
268
|
+
await db.kysely
|
|
269
|
+
.deleteFrom('migrations')
|
|
270
|
+
.where('name', '=', '0073_enabled_billing_sources_early_access_backfill')
|
|
271
|
+
.execute()
|
|
272
|
+
await db.migrate()
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const result = await Promise.all(
|
|
276
|
+
[exact, domain, invited, plain, emailLess].map(async ({ organization }) =>
|
|
277
|
+
(await EnabledBillingSources.listByOrg(db, organization.id)).map(
|
|
278
|
+
({ createdBy, source }) => ({ createdBy, source }),
|
|
279
|
+
),
|
|
280
|
+
),
|
|
281
|
+
)
|
|
282
|
+
expect(result).toMatchInlineSnapshot(`
|
|
283
|
+
[
|
|
284
|
+
[
|
|
285
|
+
{
|
|
286
|
+
"createdBy": "migration",
|
|
287
|
+
"source": "stripe",
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
[
|
|
291
|
+
{
|
|
292
|
+
"createdBy": "migration",
|
|
293
|
+
"source": "stripe",
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
[],
|
|
297
|
+
[],
|
|
298
|
+
[],
|
|
299
|
+
]
|
|
300
|
+
`)
|
|
301
|
+
await db.close()
|
|
302
|
+
})
|
|
303
|
+
})
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { Selectable } from 'kysely'
|
|
2
|
+
|
|
3
|
+
import type * as Db from '../Db.js'
|
|
4
|
+
import type * as db_Schema from '../Schema.js'
|
|
5
|
+
|
|
6
|
+
/** Columns of the `enabled_billing_sources` table, derived from `Schema.EnabledBillingSource`. */
|
|
7
|
+
export type Table = db_Schema.EnabledBillingSource
|
|
8
|
+
|
|
9
|
+
/** A stored enabled billing-source row. */
|
|
10
|
+
export type Record = Selectable<Table>
|
|
11
|
+
|
|
12
|
+
/** An enabled billing-source type. */
|
|
13
|
+
export type Source = Record['source']
|
|
14
|
+
|
|
15
|
+
/** Billing-source types in canonical display order. */
|
|
16
|
+
export const sources = ['stripe', 'tempo'] as const satisfies readonly Source[]
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Enables a billing-source type for an organization.
|
|
20
|
+
*
|
|
21
|
+
* @param db - The database.
|
|
22
|
+
* @param input - The source to enable.
|
|
23
|
+
* @returns The stored row, or `undefined` when already enabled.
|
|
24
|
+
*/
|
|
25
|
+
export function add(db: Db.Db, input: add.Input): Promise<Record | undefined> {
|
|
26
|
+
return db.kysely
|
|
27
|
+
.insertInto('enabled_billing_sources')
|
|
28
|
+
.values({
|
|
29
|
+
createdAt: new Date().toISOString(),
|
|
30
|
+
createdBy: input.createdBy,
|
|
31
|
+
orgId: input.orgId,
|
|
32
|
+
source: input.source,
|
|
33
|
+
})
|
|
34
|
+
.onConflict((oc) => oc.columns(['orgId', 'source']).doNothing())
|
|
35
|
+
.returningAll()
|
|
36
|
+
.executeTakeFirst()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export declare namespace add {
|
|
40
|
+
/** Fields accepted when enabling a billing source. */
|
|
41
|
+
type Input = {
|
|
42
|
+
/** Identity enabling the source. */
|
|
43
|
+
createdBy: string
|
|
44
|
+
/** Organization id (`org_…`) the source is enabled for. */
|
|
45
|
+
orgId: string
|
|
46
|
+
/** Billing-source type to enable. */
|
|
47
|
+
source: Source
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Lists an organization's enabled billing sources in canonical order.
|
|
53
|
+
*
|
|
54
|
+
* @param db - The database.
|
|
55
|
+
* @param orgId - The organization id (`org_…`).
|
|
56
|
+
* @returns The enabled sources.
|
|
57
|
+
*/
|
|
58
|
+
export function listByOrg(db: Db.Db, orgId: string): Promise<Record[]> {
|
|
59
|
+
return db.kysely
|
|
60
|
+
.selectFrom('enabled_billing_sources')
|
|
61
|
+
.selectAll()
|
|
62
|
+
.where('orgId', '=', orgId)
|
|
63
|
+
.orderBy('source', 'asc')
|
|
64
|
+
.execute()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Removes one enabled billing source from an organization.
|
|
69
|
+
*
|
|
70
|
+
* @param db - The database.
|
|
71
|
+
* @param input - The source to remove.
|
|
72
|
+
* @returns The removed row, or `undefined` when absent.
|
|
73
|
+
*/
|
|
74
|
+
export function remove(db: Db.Db, input: remove.Input): Promise<Record | undefined> {
|
|
75
|
+
return db.kysely
|
|
76
|
+
.deleteFrom('enabled_billing_sources')
|
|
77
|
+
.where('orgId', '=', input.orgId)
|
|
78
|
+
.where('source', '=', input.source)
|
|
79
|
+
.returningAll()
|
|
80
|
+
.executeTakeFirst()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export declare namespace remove {
|
|
84
|
+
/** Fields identifying one enabled billing source. */
|
|
85
|
+
type Input = {
|
|
86
|
+
/** Organization id (`org_…`) the source is enabled for. */
|
|
87
|
+
orgId: string
|
|
88
|
+
/** Billing-source type to remove. */
|
|
89
|
+
source: Source
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Checks whether a billing-source type is enabled for an organization.
|
|
95
|
+
*
|
|
96
|
+
* @param db - The database.
|
|
97
|
+
* @param input - The source to check.
|
|
98
|
+
* @returns Whether the source is enabled.
|
|
99
|
+
*/
|
|
100
|
+
export async function isEnabled(db: Db.Db, input: isEnabled.Input): Promise<boolean> {
|
|
101
|
+
const record = await db.kysely
|
|
102
|
+
.selectFrom('enabled_billing_sources')
|
|
103
|
+
.select('source')
|
|
104
|
+
.where('orgId', '=', input.orgId)
|
|
105
|
+
.where('source', '=', input.source)
|
|
106
|
+
.executeTakeFirst()
|
|
107
|
+
return record !== undefined
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export declare namespace isEnabled {
|
|
111
|
+
/** Fields identifying one enabled billing source. */
|
|
112
|
+
type Input = {
|
|
113
|
+
/** Organization id (`org_…`) the source is enabled for. */
|
|
114
|
+
orgId: string
|
|
115
|
+
/** Billing-source type to check. */
|
|
116
|
+
source: Source
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Selectable } from 'kysely'
|
|
2
2
|
|
|
3
3
|
import * as Id from '../../internal/Id.js'
|
|
4
4
|
import type * as Db from '../Db.js'
|
|
@@ -31,7 +31,8 @@ export function create(db: Db.Db, input: create.Input): Promise<Record> {
|
|
|
31
31
|
createdAt: now,
|
|
32
32
|
email: input.email.toLowerCase(),
|
|
33
33
|
expiresAt: input.expiresAt,
|
|
34
|
-
|
|
34
|
+
// Keep the legacy column inert until it can be removed safely.
|
|
35
|
+
grantsEarlyAccess: false,
|
|
35
36
|
id: Id.generate('inv'),
|
|
36
37
|
invitedBy: input.invitedBy,
|
|
37
38
|
orgId: input.orgId,
|
|
@@ -46,8 +47,7 @@ export function create(db: Db.Db, input: create.Input): Promise<Record> {
|
|
|
46
47
|
.doUpdateSet({
|
|
47
48
|
createdAt: now,
|
|
48
49
|
expiresAt: input.expiresAt,
|
|
49
|
-
|
|
50
|
-
grantsEarlyAccess: sql<boolean>`invitations.grants_early_access OR excluded.grants_early_access`,
|
|
50
|
+
grantsEarlyAccess: false,
|
|
51
51
|
invitedBy: input.invitedBy,
|
|
52
52
|
role: input.role,
|
|
53
53
|
}),
|
|
@@ -63,8 +63,6 @@ export declare namespace create {
|
|
|
63
63
|
email: string
|
|
64
64
|
/** When the invitation expires (ISO 8601). */
|
|
65
65
|
expiresAt: string
|
|
66
|
-
/** Whether accepting grants early access; stamped from the inviter's own access. */
|
|
67
|
-
grantsEarlyAccess: boolean
|
|
68
66
|
/** User or API key id creating the invitation. */
|
|
69
67
|
invitedBy: string
|
|
70
68
|
/** Organization id (`org_…`) the invitation joins. */
|
|
@@ -119,26 +117,6 @@ export function listByEmail(db: Db.Db, email: string): Promise<(Record & { orgNa
|
|
|
119
117
|
.execute()
|
|
120
118
|
}
|
|
121
119
|
|
|
122
|
-
/**
|
|
123
|
-
* Whether any unrevoked invitation grants `email` early access. Acceptance and
|
|
124
|
-
* expiry are deliberately ignored: a trusted invite grants access until revoked.
|
|
125
|
-
*
|
|
126
|
-
* @param db - The database.
|
|
127
|
-
* @param email - The invitee email (matched lowercase).
|
|
128
|
-
* @returns Whether a grant exists.
|
|
129
|
-
*/
|
|
130
|
-
export async function hasEarlyAccessGrant(db: Db.Db, email: string): Promise<boolean> {
|
|
131
|
-
const row = await db.kysely
|
|
132
|
-
.selectFrom('invitations')
|
|
133
|
-
.select('id')
|
|
134
|
-
.where('email', '=', email.toLowerCase())
|
|
135
|
-
.where('grantsEarlyAccess', '=', true)
|
|
136
|
-
.where('revokedAt', 'is', null)
|
|
137
|
-
.limit(1)
|
|
138
|
-
.executeTakeFirst()
|
|
139
|
-
return row !== undefined
|
|
140
|
-
}
|
|
141
|
-
|
|
142
120
|
/**
|
|
143
121
|
* Revokes a pending invitation.
|
|
144
122
|
*
|
|
@@ -3,6 +3,8 @@ import { nanoid } from 'nanoid'
|
|
|
3
3
|
import * as Runtime from '../../../test/runtime.js'
|
|
4
4
|
import * as Webhooks from '../../internal/Webhooks.js'
|
|
5
5
|
import * as Db from '../Db.js'
|
|
6
|
+
import * as EnabledBillingSources from './enabledBillingSources.js'
|
|
7
|
+
import * as Memberships from './memberships.js'
|
|
6
8
|
import * as Organizations from './organizations.js'
|
|
7
9
|
import * as SponsoredTransactions from './sponsoredTransactions.js'
|
|
8
10
|
import * as StripeCustomers from './stripeCustomers.js'
|
|
@@ -60,6 +62,66 @@ describe('create', () => {
|
|
|
60
62
|
})
|
|
61
63
|
})
|
|
62
64
|
|
|
65
|
+
describe('createOwned', () => {
|
|
66
|
+
test('behavior: creates the organization, owner, and enabled sources atomically', async () => {
|
|
67
|
+
const db = create()
|
|
68
|
+
await db.migrate()
|
|
69
|
+
const record = await Organizations.createOwned(db, {
|
|
70
|
+
enabledBillingSources: ['stripe'],
|
|
71
|
+
name: 'Acme, Inc.',
|
|
72
|
+
userId: 'usr_owner',
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
expect(
|
|
76
|
+
(await Memberships.listByOrg(db, record.id)).map(({ role, userId }) => ({ role, userId })),
|
|
77
|
+
).toMatchInlineSnapshot(`
|
|
78
|
+
[
|
|
79
|
+
{
|
|
80
|
+
"role": "owner",
|
|
81
|
+
"userId": "usr_owner",
|
|
82
|
+
},
|
|
83
|
+
]
|
|
84
|
+
`)
|
|
85
|
+
expect(
|
|
86
|
+
(await EnabledBillingSources.listByOrg(db, record.id)).map(({ createdBy, source }) => ({
|
|
87
|
+
createdBy,
|
|
88
|
+
source,
|
|
89
|
+
})),
|
|
90
|
+
).toMatchInlineSnapshot(`
|
|
91
|
+
[
|
|
92
|
+
{
|
|
93
|
+
"createdBy": "usr_owner",
|
|
94
|
+
"source": "stripe",
|
|
95
|
+
},
|
|
96
|
+
]
|
|
97
|
+
`)
|
|
98
|
+
await db.close()
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('behavior: rolls back all rows when initial source insertion fails', async () => {
|
|
102
|
+
const db = create()
|
|
103
|
+
await db.migrate()
|
|
104
|
+
|
|
105
|
+
await expect(
|
|
106
|
+
Organizations.createOwned(db, {
|
|
107
|
+
enabledBillingSources: ['stripe', 'stripe'],
|
|
108
|
+
name: 'Acme, Inc.',
|
|
109
|
+
userId: 'usr_owner',
|
|
110
|
+
}),
|
|
111
|
+
).rejects.toThrow()
|
|
112
|
+
expect(await Organizations.listByUser(db, 'usr_owner')).toEqual([])
|
|
113
|
+
expect(await db.kysely.selectFrom('enabled_billing_sources').selectAll().execute()).toEqual([])
|
|
114
|
+
expect(
|
|
115
|
+
await db.kysely
|
|
116
|
+
.selectFrom('memberships')
|
|
117
|
+
.selectAll()
|
|
118
|
+
.where('userId', '=', 'usr_owner')
|
|
119
|
+
.execute(),
|
|
120
|
+
).toEqual([])
|
|
121
|
+
await db.close()
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
63
125
|
describe('get', () => {
|
|
64
126
|
test('behavior: returns undefined when absent', async () => {
|
|
65
127
|
const db = create()
|
|
@@ -137,6 +199,11 @@ describe('deleteOrganization', () => {
|
|
|
137
199
|
orgId: 'org_1',
|
|
138
200
|
stripeCustomerId: 'cus_org_1',
|
|
139
201
|
})
|
|
202
|
+
await EnabledBillingSources.add(db, {
|
|
203
|
+
createdBy: 'admin@tempo.xyz',
|
|
204
|
+
orgId: 'org_1',
|
|
205
|
+
source: 'stripe',
|
|
206
|
+
})
|
|
140
207
|
await SponsoredTransactions.upsert(db, {
|
|
141
208
|
apiKeyId: 'key_1',
|
|
142
209
|
billable: true,
|
|
@@ -153,10 +220,46 @@ describe('deleteOrganization', () => {
|
|
|
153
220
|
Organizations.UnreportedSponsorshipsError,
|
|
154
221
|
)
|
|
155
222
|
expect(await Organizations.get(db, 'org_1')).toBeDefined()
|
|
223
|
+
expect(await EnabledBillingSources.isEnabled(db, { orgId: 'org_1', source: 'stripe' })).toBe(
|
|
224
|
+
true,
|
|
225
|
+
)
|
|
156
226
|
expect(await StripeCustomers.get(db, 'org_1')).toBeDefined()
|
|
157
227
|
await db.close()
|
|
158
228
|
})
|
|
159
229
|
|
|
230
|
+
test('behavior: deletes enabled billing sources', async () => {
|
|
231
|
+
const db = create()
|
|
232
|
+
await db.migrate()
|
|
233
|
+
await Organizations.create(db, { id: 'org_1', name: 'One' })
|
|
234
|
+
await Organizations.create(db, { id: 'org_2', name: 'Two' })
|
|
235
|
+
await EnabledBillingSources.add(db, {
|
|
236
|
+
createdBy: 'admin@tempo.xyz',
|
|
237
|
+
orgId: 'org_1',
|
|
238
|
+
source: 'stripe',
|
|
239
|
+
})
|
|
240
|
+
await EnabledBillingSources.add(db, {
|
|
241
|
+
createdBy: 'admin@tempo.xyz',
|
|
242
|
+
orgId: 'org_1',
|
|
243
|
+
source: 'tempo',
|
|
244
|
+
})
|
|
245
|
+
await EnabledBillingSources.add(db, {
|
|
246
|
+
createdBy: 'admin@tempo.xyz',
|
|
247
|
+
orgId: 'org_2',
|
|
248
|
+
source: 'stripe',
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
expect(await Organizations.deleteOrganization(db, 'org_1')).toBe(true)
|
|
252
|
+
|
|
253
|
+
expect(await EnabledBillingSources.listByOrg(db, 'org_1')).toEqual([])
|
|
254
|
+
expect((await EnabledBillingSources.listByOrg(db, 'org_2')).map((record) => record.source))
|
|
255
|
+
.toMatchInlineSnapshot(`
|
|
256
|
+
[
|
|
257
|
+
"stripe",
|
|
258
|
+
]
|
|
259
|
+
`)
|
|
260
|
+
await db.close()
|
|
261
|
+
})
|
|
262
|
+
|
|
160
263
|
test('behavior: deletes api-key-owned webhook subscriptions', async () => {
|
|
161
264
|
const db = create()
|
|
162
265
|
await db.migrate()
|