diff --git a/server/experiments/diffListEntitiesV2Responses.ts b/server/experiments/diffListEntitiesV2Responses.ts new file mode 100644 index 000000000..e0bbe1ef0 --- /dev/null +++ b/server/experiments/diffListEntitiesV2Responses.ts @@ -0,0 +1,177 @@ +// Run with `CHECK_ORG_ID=... CHECK_CUSTOMER_ID=... CHECK_LIMIT=200 bun run experiments/diffListEntitiesV2Responses.ts` +import { AppEnv, type CusProductStatus, type SubjectQueryRow } from "@autumn/shared"; +import { sql } from "drizzle-orm"; +import { initDrizzle } from "../src/db/initDrizzle.js"; +import { RELEVANT_STATUSES } from "../src/internal/customers/cusProducts/CusProductService.js"; +import { getFullSubjectRowsQuery } from "../src/internal/customers/repos/getFullSubject/getFullSubjectRowsQuery.js"; +import { mergeEntityAndCustomerSubjectRows } from "../src/internal/customers/repos/getFullSubject/mergeEntityAndCustomerSubjectRows.js"; +import { getCursorPaginatedEntitySubjectsQuery } from "../src/internal/entities/repos/cursorListEntitiesQuery.js"; +import { getCustomerLevelSubjectRowsQuery } from "../src/internal/entities/repos/customerLevelSubjectsQuery.js"; + +const ORG_ID = process.env.CHECK_ORG_ID as string; +const CUSTOMER_ID = process.env.CHECK_CUSTOMER_ID as string; +const LIMIT = Number(process.env.CHECK_LIMIT || 200); +const ENV = AppEnv.Live; + +const getCombinedQuery = ({ inStatuses }: { inStatuses: CusProductStatus[] }) => { + const customerFilter = CUSTOMER_ID ? sql`AND c.id = ${CUSTOMER_ID}` : sql``; + const leadingCtes = sql` + WITH entity_records AS ( + SELECT e.* + FROM entities e + JOIN customers c ON c.internal_id = e.internal_customer_id + WHERE e.org_id = ${ORG_ID} AND e.env = ${ENV} + AND c.org_id = ${ORG_ID} AND c.env = ${ENV} + ${customerFilter} + ORDER BY e.created_at DESC, e.id DESC + LIMIT ${LIMIT + 1} + ), + subject_records AS ( + SELECT er.internal_id AS subject_key, er.internal_customer_id, er.internal_id AS internal_entity_id, + ROW_NUMBER() OVER (ORDER BY er.created_at DESC, er.id DESC) AS subject_order + FROM entity_records er + ) + `; + return getFullSubjectRowsQuery({ + leadingCtes, + inStatuses, + includeInvoices: false, + includeEntityAggregations: false, + }); +}; + +const stableStringify = (value: unknown): string => { + if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`; + if (value && typeof value === "object") { + const entries = Object.entries(value as Record) + .filter(([, v]) => v !== undefined) + .sort(([a], [b]) => (a < b ? -1 : 1)) + .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`); + return `{${entries.join(",")}}`; + } + return JSON.stringify(value); +}; + +const sortByKey = (rows: Record[], key: string) => + [...rows].sort((a, b) => (String(a[key]) < String(b[key]) ? -1 : 1)); + +// order-insensitive fields: combined query has no deterministic ORDER BY here +const UNORDERED_FIELDS: Record = { + customer_entitlements: "id", + customer_prices: "id", + subscriptions: "stripe_id", + entitlements: "id", + rollovers: "id", + replaceables: "id", +}; +const ORDERED_FIELDS = [ + "customer", + "entity", + "customer_products", + "extra_customer_entitlements", + "products", + "prices", + "free_trials", +]; + +const main = async () => { + const { db } = initDrizzle(); + const inStatuses = RELEVANT_STATUSES; + + const combinedRows = (await db.execute( + getCombinedQuery({ inStatuses }), + )) as unknown as SubjectQueryRow[]; + + const entityRows = (await db.execute( + getCursorPaginatedEntitySubjectsQuery({ + orgId: ORG_ID, + env: ENV, + limit: LIMIT, + cursor: null, + inStatuses, + customerId: CUSTOMER_ID || undefined, + }), + )) as unknown as SubjectQueryRow[]; + + const internalCustomerIds = [ + ...new Set(entityRows.map((row) => row.customer.internal_id)), + ]; + const customerRows = + internalCustomerIds.length > 0 + ? ((await db.execute( + getCustomerLevelSubjectRowsQuery({ + orgId: ORG_ID, + env: ENV, + internalCustomerIds, + inStatuses, + }), + )) as unknown as SubjectQueryRow[]) + : []; + const customerRowsByInternalId = new Map( + customerRows.map((row) => [row.customer.internal_id, row]), + ); + + const mergedRows = entityRows.map((entityRow) => + mergeEntityAndCustomerSubjectRows({ + entityRow, + customerRow: customerRowsByInternalId.get(entityRow.customer.internal_id), + }), + ); + + console.log(`combined: ${combinedRows.length} rows, merged: ${mergedRows.length} rows`); + if (combinedRows.length !== mergedRows.length) throw new Error("row count mismatch"); + + const classifiedFields = new Set([ + ...ORDERED_FIELDS, + ...Object.keys(UNORDERED_FIELDS), + ]); + const unclassifiedFields = [ + ...new Set( + [...combinedRows, ...mergedRows].flatMap((row) => + Object.keys(row as Record), + ), + ), + ].filter((field) => !classifiedFields.has(field)); + if (unclassifiedFields.length > 0) { + console.log( + `unclassified row fields (add to ORDERED_FIELDS or UNORDERED_FIELDS): ${unclassifiedFields.join(", ")}`, + ); + process.exit(1); + } + + let mismatches = 0; + for (let i = 0; i < combinedRows.length; i++) { + const combined = combinedRows[i] as unknown as Record; + const merged = mergedRows[i] as unknown as Record; + + for (const field of ORDERED_FIELDS) { + const left = stableStringify(combined[field] ?? null); + const right = stableStringify(merged[field] ?? null); + if (left !== right) { + mismatches++; + console.log(`row ${i} entity=${(combined.entity as { id?: string })?.id} ORDERED field "${field}" differs`); + if (mismatches <= 3) { + console.log(` combined: ${left.slice(0, 500)}`); + console.log(` merged: ${right.slice(0, 500)}`); + } + } + } + for (const [field, key] of Object.entries(UNORDERED_FIELDS)) { + const left = stableStringify(sortByKey((combined[field] as Record[]) ?? [], key)); + const right = stableStringify(sortByKey((merged[field] as Record[]) ?? [], key)); + if (left !== right) { + mismatches++; + console.log(`row ${i} entity=${(combined.entity as { id?: string })?.id} UNORDERED field "${field}" differs`); + if (mismatches <= 3) { + console.log(` combined: ${left.slice(0, 500)}`); + console.log(` merged: ${right.slice(0, 500)}`); + } + } + } + } + + console.log(mismatches === 0 ? "ALL ROWS IDENTICAL" : `${mismatches} field mismatches`); + process.exit(mismatches === 0 ? 0 : 1); +}; + +await main(); diff --git a/server/experiments/explainListEntitiesV2.ts b/server/experiments/explainListEntitiesV2.ts new file mode 100644 index 000000000..157e56ca9 --- /dev/null +++ b/server/experiments/explainListEntitiesV2.ts @@ -0,0 +1,181 @@ +import { AppEnv, type CusProductStatus } from "@autumn/shared"; +import { type SQL, sql } from "drizzle-orm"; +import { RELEVANT_STATUSES } from "../src/internal/customers/cusProducts/CusProductService.js"; +import { getFullSubjectRowsQuery } from "../src/internal/customers/repos/getFullSubject/getFullSubjectRowsQuery.js"; +import { getCursorPaginatedEntitySubjectsQuery } from "../src/internal/entities/repos/cursorListEntitiesQuery.js"; +import { getCustomerLevelSubjectRowsQuery } from "../src/internal/entities/repos/customerLevelSubjectsQuery.js"; +import { + initDrizzle, + prodTestCustomerId, + prodTestOrgId, +} from "./experimentEnv"; + +// Run with `bun run experiments/explainListEntitiesV2.ts` (flags: --explain, --skip-old) +const ORG_ID = prodTestOrgId; +const ENV = AppEnv.Live; +const CUSTOMER_ID = prodTestCustomerId; +const LIMIT = Number(process.env.LIMIT || 1000); + +/** Pre-split combined hydration: same page CTE, hydrated without entityScopedOnly. */ +const getCombinedEntityPageQuery = ({ + orgId, + env, + customerId, + limit, + inStatuses, +}: { + orgId: string; + env: AppEnv; + customerId?: string; + limit: number; + inStatuses: CusProductStatus[]; +}) => { + const customerFilter = customerId ? sql`AND c.id = ${customerId}` : sql``; + + const leadingCtes = sql` + WITH entity_records AS ( + SELECT e.* + FROM entities e + JOIN customers c + ON c.internal_id = e.internal_customer_id + WHERE e.org_id = ${orgId} + AND e.env = ${env} + AND c.org_id = ${orgId} + AND c.env = ${env} + ${customerFilter} + ORDER BY e.created_at DESC, e.id DESC + LIMIT ${limit + 1} + ), + + subject_records AS ( + SELECT + er.internal_id AS subject_key, + er.internal_customer_id, + er.internal_id AS internal_entity_id, + ROW_NUMBER() OVER (ORDER BY er.created_at DESC, er.id DESC) AS subject_order + FROM entity_records er + ) + `; + + return getFullSubjectRowsQuery({ + leadingCtes, + inStatuses, + includeInvoices: false, + includeEntityAggregations: false, + }); +}; + +const printExplainPlan = async ({ + db, + query, +}: { + db: ReturnType["db"]; + query: SQL; +}) => { + const explainResult = await db.execute( + sql`EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) ${query}`, + ); + + for (const row of explainResult) { + console.log((row as Record)["QUERY PLAN"]); + } +}; + +const runMeasuredQuery = async ({ + db, + label, + query, + withExplain, +}: { + db: ReturnType["db"]; + label: string; + query: SQL; + withExplain: boolean; +}) => { + console.log(`\n=== ${label} ===\n`); + + const startedAt = performance.now(); + const result = await db.execute(query); + const elapsedMilliseconds = performance.now() - startedAt; + + console.log(`Rows returned: ${result.length}`); + console.log(`Wall-clock time: ${elapsedMilliseconds.toFixed(2)}ms\n`); + + if (withExplain) { + await printExplainPlan({ + db, + query, + }); + } +}; + +const main = async () => { + const { db } = initDrizzle(); + const inStatuses = RELEVANT_STATUSES; + const withExplain = process.argv.includes("--explain"); + const skipOld = process.argv.includes("--skip-old"); + + console.log("=== LIST ENTITIES V2 SPLIT HYDRATION EXPERIMENT ===\n"); + console.log( + JSON.stringify( + { orgId: ORG_ID, env: ENV, customerId: CUSTOMER_ID, limit: LIMIT }, + null, + 2, + ), + ); + + const customerRows = await db.execute( + sql`SELECT internal_id FROM customers + WHERE org_id = ${ORG_ID} AND env = ${ENV} AND id = ${CUSTOMER_ID}`, + ); + const internalCustomerId = (customerRows[0] as { internal_id?: string }) + ?.internal_id; + if (!internalCustomerId) { + throw new Error(`Customer ${CUSTOMER_ID} not found in org ${ORG_ID}`); + } + + if (!skipOld) { + await runMeasuredQuery({ + db, + label: "OLD COMBINED QUERY (pre-split hydration)", + query: getCombinedEntityPageQuery({ + orgId: ORG_ID, + env: ENV, + customerId: CUSTOMER_ID, + limit: LIMIT, + inStatuses, + }), + withExplain, + }); + } + + await runMeasuredQuery({ + db, + label: "NEW QUERY A (entity-scoped page hydration)", + query: getCursorPaginatedEntitySubjectsQuery({ + orgId: ORG_ID, + env: ENV, + limit: LIMIT, + cursor: null, + inStatuses, + customerId: CUSTOMER_ID, + }), + withExplain, + }); + + await runMeasuredQuery({ + db, + label: "NEW QUERY B (customer-level hydration, once per customer)", + query: getCustomerLevelSubjectRowsQuery({ + orgId: ORG_ID, + env: ENV, + internalCustomerIds: [internalCustomerId], + inStatuses, + }), + withExplain, + }); + + process.exit(0); +}; + +await main(); diff --git a/server/src/db/initDrizzle.ts b/server/src/db/initDrizzle.ts index a1331bda3..e024552f5 100644 --- a/server/src/db/initDrizzle.ts +++ b/server/src/db/initDrizzle.ts @@ -8,6 +8,7 @@ import { instrumentDrizzleClient } from "@kubiks/otel-drizzle"; import type { SQLWrapper } from "drizzle-orm"; import { drizzle } from "drizzle-orm/node-postgres"; import pg, { type PoolConfig } from "pg"; +import { logger } from "../external/logtail/logtailUtils.js"; import { otelConfig } from "../utils/otel/otelConfig.js"; import { attachPoolErrorHandlers, registerPool } from "./pgPoolMonitor.js"; @@ -97,22 +98,72 @@ export const initDrizzle = ({ // Strict latency limits in prod; relaxed locally so dev pool warm-up doesn't kill tests. const isProd = process.env.NODE_ENV === "production"; +const poolMaxFromEnv = ({ + envVar, + fallback, +}: { + envVar: string; + fallback: number; +}): number => { + const parsed = Number(process.env[envVar]); + return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback; +}; + +const PGBOUNCER_MAX_CLIENT_CONN = 7_600; +const BUDGETED_FLEET_PROCESSES = 150; +const BUDGETED_NON_SERVER_CONNECTIONS = 80; +const POOL_BUDGET_HEADROOM = 0.85; + +const PROD_POOL_MAX = { + critical: 22, + general: 14, + replica: 6, +}; + +const criticalPoolMax = poolMaxFromEnv({ + envVar: "CRITICAL_DB_POOL_MAX", + fallback: isProd ? PROD_POOL_MAX.critical : 10, +}); +const generalPoolMax = poolMaxFromEnv({ + envVar: "GENERAL_DB_POOL_MAX", + fallback: isProd ? PROD_POOL_MAX.general : 10, +}); +const replicaPoolMax = poolMaxFromEnv({ + envVar: "REPLICA_DB_POOL_MAX", + fallback: PROD_POOL_MAX.replica, +}); + +const budgetedFleetConnections = + BUDGETED_FLEET_PROCESSES * + (criticalPoolMax + generalPoolMax + replicaPoolMax) + + BUDGETED_NON_SERVER_CONNECTIONS; + +if ( + budgetedFleetConnections > + PGBOUNCER_MAX_CLIENT_CONN * POOL_BUDGET_HEADROOM +) { + logger.warn( + `[initDrizzle] pool budget (${budgetedFleetConnections}) exceeds ${POOL_BUDGET_HEADROOM} of max_client_conn (${PGBOUNCER_MAX_CLIENT_CONN}) — lower the pool maxes or raise the ceiling`, + ); +} + export const { db: dbCritical, client: clientCritical } = initDrizzle({ name: "critical", - maxConnections: isProd ? 100 : 10, + maxConnections: criticalPoolMax, connectTimeout: isProd ? 2 : 30, databaseUrl: process.env.DATABASE_CRITICAL_URL, poolConfig: { application_name: "autumn-critical", query_timeout: isProd ? 2_000 : 30_000, - // Keep 10 warm conns to avoid TLS-handshake stampedes on bursty traffic. - min: 10, + // Keep warm conns to avoid TLS-handshake stampedes on bursty traffic. + min: Math.min(10, criticalPoolMax), }, }); // -- General pool: used by all other endpoints -- export const { db: dbGeneral, client: clientGeneral } = initDrizzle({ name: "general", + maxConnections: generalPoolMax, connectTimeout: isProd ? 5 : 30, }); @@ -122,7 +173,7 @@ const replicaResult = process.env.DATABASE_REPLICA_URL ? initDrizzle({ name: "replica", replica: true, - maxConnections: 15, + maxConnections: replicaPoolMax, connectTimeout: null, }) : null; diff --git a/server/src/honoMiddlewares/rateLimitMiddleware.ts b/server/src/honoMiddlewares/rateLimitMiddleware.ts index b3e1ff4cf..6e8c85cb0 100644 --- a/server/src/honoMiddlewares/rateLimitMiddleware.ts +++ b/server/src/honoMiddlewares/rateLimitMiddleware.ts @@ -6,6 +6,7 @@ import { setRateLimitKeyInContext, } from "@/internal/misc/rateLimiter/rateLimitFactory"; import { + getOrgAggregateType, getRateLimitType, RateLimitType, } from "../internal/misc/rateLimiter/rateLimitConfigs"; @@ -39,8 +40,31 @@ export const rateLimitMiddleware = async (c: Context, next: Next) => { // 4. Get the appropriate limiter for this type const limiter = getLimiterForType(rateLimitType); - // 5. Apply rate limiting - return await limiter(c as Context, next); + const aggregateType = getOrgAggregateType(rateLimitType); + if (!aggregateType) { + // 5. Apply rate limiting + return await limiter(c as Context, next); + } + + // 5. Org-aggregate limiter wraps the per-customer one; the key slot is + // swapped between them since keyGenerator reads it at execution time. + setRateLimitKeyInContext( + c as Context, + getRateLimitKey({ c, rateLimitType: aggregateType }), + ); + const aggregateLimiter = getLimiterForType(aggregateType); + + let innerResponse: Response | undefined; + const aggregateResponse = await aggregateLimiter( + c as Context, + async () => { + setRateLimitKeyInContext(c as Context, rateLimitKey); + innerResponse = (await limiter(c as Context, next)) ?? undefined; + }, + ); + + // hono-rate-limiter discards next()'s return, so re-surface an inner 429. + return aggregateResponse ?? innerResponse; } catch (error) { ctx.logger.error( `Error checking rate limit, error: ${error}. Bypassing for now`, diff --git a/server/src/honoUtils/HonoEnv.ts b/server/src/honoUtils/HonoEnv.ts index c7f928183..3fbefb7b5 100644 --- a/server/src/honoUtils/HonoEnv.ts +++ b/server/src/honoUtils/HonoEnv.ts @@ -74,6 +74,10 @@ export type RequestContext = { fullCustomer?: FullCustomer; rolloutSnapshot?: RolloutSnapshot; + /** Org is over its aggregate rate cap — check/track flows skip the DB and + * serve their fail-open responses (allow / SQS queue) instead. */ + orgRateLimitDegraded?: boolean; + testOptions?: { skipCacheDeletion?: boolean; skipWebhooks?: boolean; diff --git a/server/src/internal/balances/check/runCheckWithRollout.ts b/server/src/internal/balances/check/runCheckWithRollout.ts index eda61ab2f..17cd689b0 100644 --- a/server/src/internal/balances/check/runCheckWithRollout.ts +++ b/server/src/internal/balances/check/runCheckWithRollout.ts @@ -18,6 +18,18 @@ export const runCheckWithRollout = async ({ body: ParsedCheckParams; requiredBalance: number; }): Promise> => { + if (ctx.orgRateLimitDegraded) { + return { + checkData: null, + response: getCheckFailOpenFallback({ + ctx, + body, + requiredBalance, + error: new Error("org aggregate rate cap exceeded"), + }) as Record, + }; + } + if (!isFullSubjectRolloutEnabled({ ctx })) { return runCheckLegacyFlow({ ctx, body, requiredBalance }); } diff --git a/server/src/internal/balances/track/runTrackWithRollout.ts b/server/src/internal/balances/track/runTrackWithRollout.ts index 23e88a116..622590075 100644 --- a/server/src/internal/balances/track/runTrackWithRollout.ts +++ b/server/src/internal/balances/track/runTrackWithRollout.ts @@ -24,6 +24,11 @@ export const runTrackWithRollout = async ({ apiVersion?: ApiVersion; }): Promise => { if (shouldUseTrackV3({ ctx })) { + if (ctx.orgRateLimitDegraded) { + const queuedResponse = await queueTrack({ ctx, body }); + if (queuedResponse) return queuedResponse; + } + return withRedisFailOpen({ source: "runTrackWithRollout", run: () => diff --git a/server/src/internal/customers/repos/getFullSubject/getFullSubjectRowsQuery.ts b/server/src/internal/customers/repos/getFullSubject/getFullSubjectRowsQuery.ts index 5e876fecc..698fd967a 100644 --- a/server/src/internal/customers/repos/getFullSubject/getFullSubjectRowsQuery.ts +++ b/server/src/internal/customers/repos/getFullSubject/getFullSubjectRowsQuery.ts @@ -2,8 +2,39 @@ import { type CusProductStatus, RELEVANT_STATUSES } from "@autumn/shared"; import { type SQL, sql } from "drizzle-orm"; import { getEntityAggregateFragments } from "./getEntityAggregateFragments.js"; -const CUSTOMER_PRODUCT_LIMIT = 200; -const EXTRA_CUSTOMER_ENTITLEMENT_LIMIT = 200; +export const CUSTOMER_PRODUCT_LIMIT = 200; +export const EXTRA_CUSTOMER_ENTITLEMENT_LIMIT = 200; + +/** Aggregate CTE → SubjectQueryRow column. Each CTE must expose (subject_key, items). */ +const SUBJECT_AGGREGATES = [ + { cte: "cus_products_agg", column: "customer_products" }, + { cte: "cus_entitlements_agg", column: "customer_entitlements" }, + { cte: "cus_prices_agg", column: "customer_prices" }, + { cte: "extra_cus_entitlements_agg", column: "extra_customer_entitlements" }, + { cte: "replaceables_agg", column: "replaceables" }, + { cte: "rollovers_agg", column: "rollovers" }, + { cte: "products_agg", column: "products" }, + { cte: "entitlements_agg", column: "entitlements" }, + { cte: "prices_agg", column: "prices" }, + { cte: "free_trials_agg", column: "free_trials" }, + { cte: "subscriptions_agg", column: "subscriptions" }, +] as const; + +const aggregateSelects = sql.join( + SUBJECT_AGGREGATES.map(({ cte, column }) => + sql.raw(`COALESCE(${cte}.items, '[]'::json) AS ${column}`), + ), + sql`, + `, +); + +const aggregateJoins = sql.join( + SUBJECT_AGGREGATES.map(({ cte }) => + sql.raw(`LEFT JOIN ${cte} ON ${cte}.subject_key = sr.subject_key`), + ), + sql` + `, +); const emptyEntityFragments = { ctes: sql``, @@ -19,11 +50,14 @@ export const getFullSubjectRowsQuery = ({ inStatuses, includeInvoices, includeEntityAggregations, + entityScopedOnly = false, }: { leadingCtes: SQL; inStatuses: CusProductStatus[]; includeInvoices: boolean; includeEntityAggregations: boolean; + /** Only hydrate rows scoped to the subject's entity (requires non-null internal_entity_id on every subject). Customer-level rows must be merged back in separately. */ + entityScopedOnly?: boolean; }) => { const statusFilter = inStatuses.length > 0 @@ -50,6 +84,29 @@ export const getFullSubjectRowsQuery = ({ }) : emptyEntityFragments; + const customerProductSubjectPredicate = entityScopedOnly + ? sql`cp.internal_entity_id = sr.internal_entity_id` + : sql`cp.internal_customer_id = sr.internal_customer_id + AND ( + (sr.internal_entity_id IS NULL AND cp.internal_entity_id IS NULL) + OR + (sr.internal_entity_id IS NOT NULL AND ( + cp.internal_entity_id IS NULL + OR cp.internal_entity_id = sr.internal_entity_id + )) + )`; + + const customerEntitlementSubjectPredicate = entityScopedOnly + ? sql`AND ce.internal_entity_id = sr.internal_entity_id` + : sql`AND ( + (sr.internal_entity_id IS NULL AND ce.internal_entity_id IS NULL) + OR + (sr.internal_entity_id IS NOT NULL AND ( + ce.internal_entity_id IS NULL + OR ce.internal_entity_id = sr.internal_entity_id + )) + )`; + const invoicesCte = includeInvoices ? sql`, @@ -83,7 +140,7 @@ export const getFullSubjectRowsQuery = ({ ${leadingCtes} , - subject_customer_records AS ( + subject_customer_records AS MATERIALIZED ( SELECT DISTINCT c.* FROM customers c JOIN subject_records sr @@ -119,15 +176,7 @@ export const getFullSubjectRowsQuery = ({ FROM customer_products cp JOIN products prod ON prod.internal_id = cp.internal_product_id - WHERE cp.internal_customer_id = sr.internal_customer_id - AND ( - (sr.internal_entity_id IS NULL AND cp.internal_entity_id IS NULL) - OR - (sr.internal_entity_id IS NOT NULL AND ( - cp.internal_entity_id IS NULL - OR cp.internal_entity_id = sr.internal_entity_id - )) - ) + WHERE ${customerProductSubjectPredicate} ${statusFilter} ) cp_candidates ON true ), @@ -175,14 +224,7 @@ export const getFullSubjectRowsQuery = ({ AND f.type = 'boolean' ) ) - AND ( - (sr.internal_entity_id IS NULL AND ce.internal_entity_id IS NULL) - OR - (sr.internal_entity_id IS NOT NULL AND ( - ce.internal_entity_id IS NULL - OR ce.internal_entity_id = sr.internal_entity_id - )) - ) + ${customerEntitlementSubjectPredicate} ORDER BY subject_entity_priority ASC, ce.id DESC LIMIT ${EXTRA_CUSTOMER_ENTITLEMENT_LIMIT} ) ce_ordered ON true @@ -300,149 +342,147 @@ export const getFullSubjectRowsQuery = ({ ${entityFragments.freeTrialRefsUnion} ) src ON ft.id = src.free_trial_id ORDER BY src.subject_key, ft.id + ), + + cus_products_agg AS ( + SELECT + cp.subject_key, + json_agg( + ( + row_to_json(cp)::jsonb + - 'subject_key' + - 'subject_entity_priority' + - 'status_priority' + - 'has_customer_prices' + - 'product_is_add_on' + - 'subject_rank' + )::json + ORDER BY + cp.subject_entity_priority ASC, + cp.status_priority ASC, + cp.has_customer_prices DESC, + cp.product_is_add_on ASC, + cp.created_at DESC + ) AS items + FROM cus_products cp + GROUP BY cp.subject_key + ), + + cus_entitlements_agg AS ( + SELECT + ce.subject_key, + json_agg((row_to_json(ce)::jsonb - 'subject_key')::json) AS items + FROM cus_entitlements ce + GROUP BY ce.subject_key + ), + + cus_prices_agg AS ( + SELECT + cpr.subject_key, + json_agg((row_to_json(cpr)::jsonb - 'subject_key')::json) AS items + FROM cus_prices cpr + GROUP BY cpr.subject_key + ), + + extra_cus_entitlements_agg AS ( + SELECT + ece.subject_key, + json_agg( + ( + row_to_json(ece)::jsonb + - 'subject_key' + - 'subject_entity_priority' + )::json + ORDER BY ece.subject_entity_priority ASC, ece.id DESC + ) AS items + FROM extra_cus_entitlements ece + GROUP BY ece.subject_key + ), + + replaceables_agg AS ( + SELECT + ace.subject_key, + json_agg(row_to_json(rep) ORDER BY rep.created_at ASC, rep.id ASC) AS items + FROM cus_replaceables rep + JOIN all_cus_ent_ids ace + ON ace.id = rep.cus_ent_id + GROUP BY ace.subject_key + ), + + rollovers_agg AS ( + SELECT + ace.subject_key, + json_agg( + row_to_json(ro) + ORDER BY ro.expires_at ASC NULLS LAST, ro.id ASC + ) AS items + FROM cus_rollovers ro + JOIN all_cus_ent_ids ace + ON ace.id = ro.cus_ent_id + GROUP BY ace.subject_key + ), + + products_agg AS ( + SELECT + p.subject_key, + json_agg( + (row_to_json(p)::jsonb - 'internal_customer_id' - 'subject_key')::json + ORDER BY p.internal_id + ) AS items + FROM distinct_products p + GROUP BY p.subject_key + ), + + entitlements_agg AS ( + SELECT + ent.subject_key, + json_agg((row_to_json(ent)::jsonb - 'internal_customer_id' - 'subject_key')::json) AS items + FROM distinct_entitlements ent + GROUP BY ent.subject_key + ), + + prices_agg AS ( + SELECT + pr.subject_key, + json_agg( + (row_to_json(pr)::jsonb - 'internal_customer_id' - 'subject_key')::json + ORDER BY pr.id + ) AS items + FROM distinct_prices pr + GROUP BY pr.subject_key + ), + + free_trials_agg AS ( + SELECT + ft.subject_key, + json_agg( + (row_to_json(ft)::jsonb - 'internal_customer_id' - 'subject_key')::json + ORDER BY ft.id + ) AS items + FROM distinct_free_trials ft + GROUP BY ft.subject_key + ), + + subscriptions_agg AS ( + SELECT + cs.subject_key, + json_agg(row_to_json(cs.subscription_row)) + FILTER (WHERE (cs.subscription_row).stripe_id IS NOT NULL) AS items + FROM ( + SELECT DISTINCT + cp.subject_key, + s AS subscription_row + FROM cus_products cp + JOIN LATERAL unnest(cp.subscription_ids) AS cp_sub(stripe_id) ON true + JOIN subscriptions s + ON s.stripe_id = cp_sub.stripe_id + ) cs + GROUP BY cs.subject_key ) SELECT row_to_json(scr) AS customer, - - COALESCE( - ( - SELECT json_agg( - ( - row_to_json(cp)::jsonb - - 'subject_key' - - 'subject_entity_priority' - - 'status_priority' - - 'has_customer_prices' - - 'product_is_add_on' - - 'subject_rank' - )::json - ORDER BY - cp.subject_entity_priority ASC, - cp.status_priority ASC, - cp.has_customer_prices DESC, - cp.product_is_add_on ASC, - cp.created_at DESC - ) - FROM cus_products cp - WHERE cp.subject_key = sr.subject_key - ), - '[]'::json - ) AS customer_products, - - COALESCE( - ( - SELECT json_agg((row_to_json(ce)::jsonb - 'subject_key')::json) - FROM cus_entitlements ce - WHERE ce.subject_key = sr.subject_key - ), - '[]'::json - ) AS customer_entitlements, - - COALESCE( - ( - SELECT json_agg((row_to_json(cpr)::jsonb - 'subject_key')::json) - FROM cus_prices cpr - WHERE cpr.subject_key = sr.subject_key - ), - '[]'::json - ) AS customer_prices, - - COALESCE( - ( - SELECT json_agg( - ( - row_to_json(ece)::jsonb - - 'subject_key' - - 'subject_entity_priority' - )::json - ORDER BY ece.subject_entity_priority ASC, ece.id DESC - ) - FROM extra_cus_entitlements ece - WHERE ece.subject_key = sr.subject_key - ), - '[]'::json - ) AS extra_customer_entitlements, - - COALESCE( - ( - SELECT json_agg(row_to_json(rep) ORDER BY rep.created_at ASC, rep.id ASC) - FROM cus_replaceables rep - WHERE rep.cus_ent_id IN ( - SELECT ace.id - FROM all_cus_ent_ids ace - WHERE ace.subject_key = sr.subject_key - ) - ), - '[]'::json - ) AS replaceables, - - COALESCE( - ( - SELECT json_agg( - row_to_json(ro) - ORDER BY ro.expires_at ASC NULLS LAST, ro.id ASC - ) - FROM cus_rollovers ro - WHERE ro.cus_ent_id IN ( - SELECT ace.id - FROM all_cus_ent_ids ace - WHERE ace.subject_key = sr.subject_key - ) - ), - '[]'::json - ) AS rollovers, - - COALESCE( - ( - SELECT json_agg((row_to_json(p)::jsonb - 'internal_customer_id' - 'subject_key')::json) - FROM distinct_products p - WHERE p.subject_key = sr.subject_key - ), - '[]'::json - ) AS products, - - COALESCE( - ( - SELECT json_agg((row_to_json(ent)::jsonb - 'internal_customer_id' - 'subject_key')::json) - FROM distinct_entitlements ent - WHERE ent.subject_key = sr.subject_key - ), - '[]'::json - ) AS entitlements, - - COALESCE( - ( - SELECT json_agg((row_to_json(pr)::jsonb - 'internal_customer_id' - 'subject_key')::json) - FROM distinct_prices pr - WHERE pr.subject_key = sr.subject_key - ), - '[]'::json - ) AS prices, - - COALESCE( - ( - SELECT json_agg((row_to_json(ft)::jsonb - 'internal_customer_id' - 'subject_key')::json) - FROM distinct_free_trials ft - WHERE ft.subject_key = sr.subject_key - ), - '[]'::json - ) AS free_trials, - - COALESCE( - ( - SELECT json_agg(row_to_json(cs)) FILTER (WHERE cs.stripe_id IS NOT NULL) - FROM ( - SELECT DISTINCT s.* - FROM cus_products cp - JOIN LATERAL unnest(cp.subscription_ids) AS cp_sub(stripe_id) ON true - JOIN subscriptions s - ON s.stripe_id = cp_sub.stripe_id - WHERE cp.subject_key = sr.subject_key - ) cs - ), - '[]'::json - ) AS subscriptions + ${aggregateSelects} ${invoicesSelect}, @@ -455,6 +495,7 @@ export const getFullSubjectRowsQuery = ({ FROM subject_records sr JOIN subject_customer_records scr ON scr.internal_id = sr.internal_customer_id + ${aggregateJoins} LEFT JOIN entities er ON er.internal_id = sr.internal_entity_id ORDER BY sr.subject_order diff --git a/server/src/internal/customers/repos/getFullSubject/mergeEntityAndCustomerSubjectRows.ts b/server/src/internal/customers/repos/getFullSubject/mergeEntityAndCustomerSubjectRows.ts new file mode 100644 index 000000000..73ad4c968 --- /dev/null +++ b/server/src/internal/customers/repos/getFullSubject/mergeEntityAndCustomerSubjectRows.ts @@ -0,0 +1,136 @@ +import type { SubjectQueryRow } from "@autumn/shared"; +import { + CUSTOMER_PRODUCT_LIMIT, + EXTRA_CUSTOMER_ENTITLEMENT_LIMIT, +} from "./getFullSubjectRowsQuery.js"; + +const dedupeBy = (rows: T[], getKey: (row: T) => string): T[] => { + const seen = new Map(); + for (const row of rows) { + if (!seen.has(getKey(row))) seen.set(getKey(row), row); + } + return [...seen.values()]; +}; + +/** Dedupe, keep only referenced rows, and sort by key to mirror the SQL's DISTINCT ON ... ORDER BY output. */ +const mergeCatalog = ( + rows: T[], + getKey: (row: T) => string, + keptKeys: Set, +): T[] => + dedupeBy(rows, getKey) + .filter((row) => keptKeys.has(getKey(row))) + .sort((left, right) => (getKey(left) < getKey(right) ? -1 : 1)); + +/** + * Recombines an entityScopedOnly subject row with its customer's + * customer-level row into the SubjectQueryRow the combined query would + * produce. Entity rows concat before customer rows (subject_entity_priority + * leads the SQL ranking), the caps apply to the combined arrays, and since + * the SQL derives every other array AFTER the caps, rows referencing + * capped-out customer products are dropped here too. + */ +export const mergeEntityAndCustomerSubjectRows = ({ + entityRow, + customerRow, +}: { + entityRow: SubjectQueryRow; + customerRow: SubjectQueryRow | undefined; +}): SubjectQueryRow => { + if (!customerRow) return entityRow; + + // The entityScopedOnly query matches on internal_entity_id alone (adding the + // customer predicate degrades its plan), so enforce the customer match here. + // Dependent rows of any dropped product are filtered transitively below. + const customerProducts = [ + ...entityRow.customer_products.filter( + (product) => + product.internal_customer_id === entityRow.customer.internal_id, + ), + ...customerRow.customer_products, + ].slice(0, CUSTOMER_PRODUCT_LIMIT); + + const extraCustomerEntitlements = [ + ...entityRow.extra_customer_entitlements, + ...customerRow.extra_customer_entitlements, + ].slice(0, EXTRA_CUSTOMER_ENTITLEMENT_LIMIT); + + const keptProductIds = new Set( + customerProducts.map((product) => product.id), + ); + + const customerEntitlements = [ + ...entityRow.customer_entitlements, + ...customerRow.customer_entitlements, + ].filter((entitlement) => keptProductIds.has(entitlement.customer_product_id)); + + const customerPrices = [ + ...entityRow.customer_prices, + ...customerRow.customer_prices, + ].filter((price) => keptProductIds.has(price.customer_product_id)); + + const keptCusEntIds = new Set( + [...customerEntitlements, ...extraCustomerEntitlements].map((ce) => ce.id), + ); + const keptRefs = { + products: new Set( + customerProducts.map((p) => p.internal_product_id), + ), + prices: new Set(customerPrices.map((p) => p.price_id)), + entitlements: new Set( + [...customerEntitlements, ...extraCustomerEntitlements].map( + (ce) => ce.entitlement_id, + ), + ), + freeTrials: new Set( + customerProducts.map((p) => p.free_trial_id), + ), + subscriptionIds: new Set( + customerProducts.flatMap((p) => p.subscription_ids ?? []), + ), + }; + + // Explicit keys only (no spreads): a new required SubjectQueryRow field must + // fail compilation here until this merge handles it. + return { + customer: entityRow.customer, + entity: entityRow.entity, + customer_products: customerProducts, + customer_entitlements: customerEntitlements, + customer_prices: customerPrices, + extra_customer_entitlements: extraCustomerEntitlements, + rollovers: [...entityRow.rollovers, ...customerRow.rollovers].filter( + (rollover) => keptCusEntIds.has(rollover.cus_ent_id), + ), + replaceables: [ + ...entityRow.replaceables, + ...customerRow.replaceables, + ].filter((replaceable) => keptCusEntIds.has(replaceable.cus_ent_id)), + products: mergeCatalog( + [...entityRow.products, ...customerRow.products], + (p) => p.internal_id, + keptRefs.products, + ), + entitlements: dedupeBy( + [...entityRow.entitlements, ...customerRow.entitlements], + (e) => e.id, + ).filter((e) => keptRefs.entitlements.has(e.id)), + prices: mergeCatalog( + [...entityRow.prices, ...customerRow.prices], + (p) => p.id, + keptRefs.prices, + ), + free_trials: mergeCatalog( + [...entityRow.free_trials, ...customerRow.free_trials], + (ft) => ft.id, + keptRefs.freeTrials, + ), + subscriptions: dedupeBy( + [...entityRow.subscriptions, ...customerRow.subscriptions], + (s) => s.stripe_id ?? "", + ).filter( + (s) => + s.stripe_id !== null && keptRefs.subscriptionIds.has(s.stripe_id), + ), + }; +}; diff --git a/server/src/internal/entities/handlers/handleListEntitiesV2.ts b/server/src/internal/entities/handlers/handleListEntitiesV2.ts index efc1ac726..371a150b2 100644 --- a/server/src/internal/entities/handlers/handleListEntitiesV2.ts +++ b/server/src/internal/entities/handlers/handleListEntitiesV2.ts @@ -30,6 +30,7 @@ import { resultToFullSubject } from "@/internal/customers/repos/getFullSubject/i import { getOrgPaginationMaxLimit } from "../../misc/edgeConfig/orgLimitsStore.js"; import { getApiEntityBaseV2 } from "../entityUtils/getApiEntityV2/getApiEntityBaseV2.js"; import { getCursorPaginatedEntitySubjectsQuery } from "../repos/cursorListEntitiesQuery.js"; +import { hydrateEntityRowsWithCustomerData } from "../repos/hydrateEntityRowsWithCustomerData.js"; import { countEntitiesByOrgIdAndEnv, countFilteredEntitiesByOrgIdAndEnv, @@ -50,13 +51,21 @@ const getListEntitiesStatuses = ({ const buildApiEntitiesFromRows = async ({ ctx, rows, + inStatuses, }: { ctx: RequestContext; rows: unknown[]; + inStatuses: CusProductStatus[]; }) => { - const fullSubjects = rows.map((row) => + const mergedRows = await hydrateEntityRowsWithCustomerData({ + ctx, + entityRows: rows as unknown as SubjectQueryRow[], + inStatuses, + }); + + const fullSubjects = mergedRows.map((row) => resultToFullSubject({ - row: row as unknown as SubjectQueryRow, + row, entityIdRequested: true, }), ); @@ -141,7 +150,11 @@ const runOffsetListEntities = async ({ }) : totalCount; - const entities = await buildApiEntitiesFromRows({ ctx, rows: subjectRows }); + const entities = await buildApiEntitiesFromRows({ + ctx, + rows: subjectRows, + inStatuses, + }); const hasMore = body.offset + entities.length < totalFilteredCount; @@ -207,7 +220,11 @@ export const handleListEntitiesV2 = createRoute({ const hasMore = rows.length > body.limit; const pageRows = hasMore ? rows.slice(0, body.limit) : rows; - const entities = await buildApiEntitiesFromRows({ ctx, rows: pageRows }); + const entities = await buildApiEntitiesFromRows({ + ctx, + rows: pageRows, + inStatuses, + }); const lastRow = pageRows[pageRows.length - 1] as | { entity?: { id?: string; created_at?: number | string } } diff --git a/server/src/internal/entities/repos/cursorListEntitiesQuery.ts b/server/src/internal/entities/repos/cursorListEntitiesQuery.ts index 86d595324..52b33dc7d 100644 --- a/server/src/internal/entities/repos/cursorListEntitiesQuery.ts +++ b/server/src/internal/entities/repos/cursorListEntitiesQuery.ts @@ -151,5 +151,6 @@ export const getCursorPaginatedEntitySubjectsQuery = ({ inStatuses, includeInvoices: false, includeEntityAggregations: false, + entityScopedOnly: true, }); }; diff --git a/server/src/internal/entities/repos/customerLevelSubjectsQuery.ts b/server/src/internal/entities/repos/customerLevelSubjectsQuery.ts new file mode 100644 index 000000000..d73f0724b --- /dev/null +++ b/server/src/internal/entities/repos/customerLevelSubjectsQuery.ts @@ -0,0 +1,42 @@ +import type { AppEnv, CusProductStatus } from "@autumn/shared"; +import { sql } from "drizzle-orm"; +import { getFullSubjectRowsQuery } from "@/internal/customers/repos/getFullSubject/getFullSubjectRowsQuery.js"; + +/** Hydrates customer-level subject rows once per customer, merged back into entity list rows by mergeEntityAndCustomerSubjectRows. */ +export const getCustomerLevelSubjectRowsQuery = ({ + orgId, + env, + internalCustomerIds, + inStatuses, +}: { + orgId: string; + env: AppEnv; + internalCustomerIds: string[]; + inStatuses: CusProductStatus[]; +}) => { + const idList = sql.join( + internalCustomerIds.map((internalCustomerId) => sql`${internalCustomerId}`), + sql`, `, + ); + + const leadingCtes = sql` + WITH subject_records AS ( + SELECT + c.internal_id AS subject_key, + c.internal_id AS internal_customer_id, + NULL::text AS internal_entity_id, + ROW_NUMBER() OVER (ORDER BY c.internal_id) AS subject_order + FROM customers c + WHERE c.internal_id IN (${idList}) + AND c.org_id = ${orgId} + AND c.env = ${env} + ) + `; + + return getFullSubjectRowsQuery({ + leadingCtes, + inStatuses, + includeInvoices: false, + includeEntityAggregations: false, + }); +}; diff --git a/server/src/internal/entities/repos/hydrateEntityRowsWithCustomerData.ts b/server/src/internal/entities/repos/hydrateEntityRowsWithCustomerData.ts new file mode 100644 index 000000000..acae12a7c --- /dev/null +++ b/server/src/internal/entities/repos/hydrateEntityRowsWithCustomerData.ts @@ -0,0 +1,46 @@ +import type { CusProductStatus, SubjectQueryRow } from "@autumn/shared"; +import type { RequestContext } from "@/honoUtils/HonoEnv.js"; +import { mergeEntityAndCustomerSubjectRows } from "@/internal/customers/repos/getFullSubject/mergeEntityAndCustomerSubjectRows.js"; +import { getCustomerLevelSubjectRowsQuery } from "./customerLevelSubjectsQuery.js"; + +/** Fetches customer-level data once per distinct customer on the page and merges it into each entityScopedOnly row. */ +export const hydrateEntityRowsWithCustomerData = async ({ + ctx, + entityRows, + inStatuses, +}: { + ctx: RequestContext; + entityRows: SubjectQueryRow[]; + inStatuses: CusProductStatus[]; +}): Promise => { + if (entityRows.length === 0) return entityRows; + + const internalCustomerIds = [ + ...new Set(entityRows.map((row) => row.customer.internal_id)), + ]; + + const customerRows = (await ctx.db.execute( + getCustomerLevelSubjectRowsQuery({ + orgId: ctx.org.id, + env: ctx.env, + internalCustomerIds, + inStatuses, + }), + )) as unknown as SubjectQueryRow[]; + + const customerRowsByInternalId = new Map( + customerRows.map((row) => [row.customer.internal_id, row]), + ); + + return entityRows.map((entityRow) => { + const customerRow = customerRowsByInternalId.get( + entityRow.customer.internal_id, + ); + if (!customerRow) { + ctx.logger.warn( + `[hydrateEntityRowsWithCustomerData] missing customer-level row for internal customer id ${entityRow.customer.internal_id}`, + ); + } + return mergeEntityAndCustomerSubjectRows({ entityRow, customerRow }); + }); +}; diff --git a/server/src/internal/entities/repos/listEntitiesQuery.ts b/server/src/internal/entities/repos/listEntitiesQuery.ts index 8ea5fae40..fe49db123 100644 --- a/server/src/internal/entities/repos/listEntitiesQuery.ts +++ b/server/src/internal/entities/repos/listEntitiesQuery.ts @@ -174,6 +174,7 @@ export const getPaginatedEntitySubjectsQuery = ({ inStatuses, includeInvoices: false, includeEntityAggregations: false, + entityScopedOnly: true, }); }; diff --git a/server/src/internal/misc/rateLimiter/rateLimitConfigs.ts b/server/src/internal/misc/rateLimiter/rateLimitConfigs.ts index 52a5f9cf1..821e6f2dd 100644 --- a/server/src/internal/misc/rateLimiter/rateLimitConfigs.ts +++ b/server/src/internal/misc/rateLimiter/rateLimitConfigs.ts @@ -13,8 +13,23 @@ export enum RateLimitType { ListCustomers = "list_customers", CustomerEntitiesGet = "customer_entities_get", Logs = "logs", + TrackOrg = "track_org", + CheckOrg = "check_org", + EntitiesGetOrg = "entities_get_org", } +// Org-wide aggregate caps summed across all of an org's customers — the +// per-customer limits never bind for many-customer storms (2026-06-08 incident). +const ORG_AGGREGATE_TYPES: Partial> = { + [RateLimitType.Track]: RateLimitType.TrackOrg, + [RateLimitType.Check]: RateLimitType.CheckOrg, + [RateLimitType.CustomerEntitiesGet]: RateLimitType.EntitiesGetOrg, +}; + +export const getOrgAggregateType = ( + type: RateLimitType, +): RateLimitType | undefined => ORG_AGGREGATE_TYPES[type]; + type RoutePattern = { method: string; url: string; @@ -120,6 +135,22 @@ const RATE_LIMIT_ROUTE_GROUPS: RateLimitRouteGroup[] = [ }, ]; +// Check-group routes that can fail open (allowed: true) when an org is over +// its aggregate cap; the establish routes in the group shed a 503 instead. +const CHECK_FAIL_OPEN_PATTERNS: RoutePattern[] = [ + route({ method: "POST", url: "/v1/check" }), + route({ method: "POST", url: "/v1/entitled" }), + route({ method: "POST", url: "/v1/balances.check" }), +]; + +export const isCheckFailOpenRoute = (c: Context): boolean => { + const method = c.req.method; + const path = c.req.path; + return CHECK_FAIL_OPEN_PATTERNS.some((pattern) => + matchRoute({ url: path, method, pattern }), + ); +}; + export const getRateLimitType = (c: Context) => { const method = c.req.method; const path = c.req.path; @@ -154,6 +185,9 @@ export type RateLimitConfig = { windowMs: number; notInRedis: boolean; scope: RateLimitScope; + // "degrade" = over-limit requests fail open (check -> allow, track -> SQS + // queue) instead of 429, so the cap sheds DB load without losing events. + overLimit?: "reject" | "degrade"; }; export const resolveRateLimit = ({ @@ -255,4 +289,29 @@ export const RATE_LIMIT_CONFIGS: Record = { notInRedis: false, scope: RateLimitScope.Org, }, + // 60s windows sized ~1.5-2x the highest legit per-org peak observed over 7d + // of prod traffic (check 157k/min, track 60k/min, entities.get 53k/min). + [RateLimitType.TrackOrg]: { + name: "track_org", + limit: 120_000, + windowMs: 60_000, + notInRedis: false, + scope: RateLimitScope.Org, + overLimit: "degrade", + }, + [RateLimitType.CheckOrg]: { + name: "check_org", + limit: 240_000, + windowMs: 60_000, + notInRedis: false, + scope: RateLimitScope.Org, + overLimit: "degrade", + }, + [RateLimitType.EntitiesGetOrg]: { + name: "entities_get_org", + limit: 90_000, + windowMs: 60_000, + notInRedis: false, + scope: RateLimitScope.Org, + }, }; diff --git a/server/src/internal/misc/rateLimiter/rateLimitFactory.ts b/server/src/internal/misc/rateLimiter/rateLimitFactory.ts index 7ae7fe61a..5371fa09f 100644 --- a/server/src/internal/misc/rateLimiter/rateLimitFactory.ts +++ b/server/src/internal/misc/rateLimiter/rateLimitFactory.ts @@ -1,14 +1,15 @@ import type { ApiVersion } from "@autumn/shared"; -import type { Context } from "hono"; +import type { Context, Next } from "hono"; import { rateLimiter } from "hono-rate-limiter"; import { logger } from "@/external/logtail/logtailUtils.js"; import { shouldUseRedis } from "@/external/redis/initRedis"; import type { HonoEnv } from "@/honoUtils/HonoEnv"; import { + isCheckFailOpenRoute, RATE_LIMIT_CONFIGS, type RateLimitConfig, RateLimitScope, - type RateLimitType, + RateLimitType, resolveRateLimit, } from "./rateLimitConfigs"; import { getOrgRateLimitOverride } from "./rateLimitOverridesStore"; @@ -60,11 +61,38 @@ export const rateLimitFactory = ({ return resolveRateLimit({ config, apiVersion }).limit; }; + // Over-limit "degrade": fail open instead of 429 — check routes get the + // allow-fallback via the ctx flag; establish routes shed a retryable 503. + const degradeHandler = async ( + c: Context, + next: Next, + ): Promise => { + const honoContext = c as Context; + const ctx = honoContext.get("ctx"); + + if (type === RateLimitType.CheckOrg && !isCheckFailOpenRoute(honoContext)) { + return c.json( + { + message: "Service is temporarily unavailable, please retry shortly.", + code: "service_unavailable", + env: ctx?.env, + }, + 503, + ); + } + + if (ctx) ctx.orgRateLimitDegraded = true; + c.header("Retry-After", undefined); + await next(); + return; + }; + const options = { windowMs, limit: dynamicLimit, standardHeaders: "draft-6" as const, keyGenerator: getRateLimitKeyFromContext, + ...(config.overLimit === "degrade" && { handler: degradeHandler }), }; let inMemoryLimiter: ReturnType | null = null; diff --git a/server/tests/unit/customers/merge-entity-and-customer-subject-rows.test.ts b/server/tests/unit/customers/merge-entity-and-customer-subject-rows.test.ts new file mode 100644 index 000000000..6d56d9eb9 --- /dev/null +++ b/server/tests/unit/customers/merge-entity-and-customer-subject-rows.test.ts @@ -0,0 +1,432 @@ +import { describe, expect, test } from "bun:test"; +import type { + DbCustomerEntitlement, + DbCustomerPrice, + DbCustomerProduct, + SubjectQueryRow, +} from "@autumn/shared"; +import { + CUSTOMER_PRODUCT_LIMIT, + EXTRA_CUSTOMER_ENTITLEMENT_LIMIT, +} from "@/internal/customers/repos/getFullSubject/getFullSubjectRowsQuery.js"; +import { mergeEntityAndCustomerSubjectRows } from "@/internal/customers/repos/getFullSubject/mergeEntityAndCustomerSubjectRows.js"; + +const createCustomerProduct = ({ + id, + internalProductId = `prod_internal_${id}`, + internalCustomerId = "cus_internal_1", + freeTrialId = null, + subscriptionIds = [], +}: { + id: string; + internalProductId?: string; + internalCustomerId?: string; + freeTrialId?: string | null; + subscriptionIds?: string[]; +}) => + ({ + id, + internal_product_id: internalProductId, + internal_customer_id: internalCustomerId, + free_trial_id: freeTrialId, + subscription_ids: subscriptionIds, + }) as DbCustomerProduct; + +const createCustomerEntitlement = ({ + id, + customerProductId, + entitlementId = `ent_${id}`, +}: { + id: string; + customerProductId: string | null; + entitlementId?: string; +}) => + ({ + id, + customer_product_id: customerProductId, + entitlement_id: entitlementId, + }) as DbCustomerEntitlement; + +const createCustomerPrice = ({ + id, + customerProductId, + priceId = `price_${id}`, +}: { + id: string; + customerProductId: string | null; + priceId?: string; +}) => + ({ + id, + customer_product_id: customerProductId, + price_id: priceId, + }) as DbCustomerPrice; + +const createRow = (overrides: Partial = {}): SubjectQueryRow => + ({ + customer: { internal_id: "cus_internal_1" }, + customer_products: [], + customer_entitlements: [], + customer_prices: [], + extra_customer_entitlements: [], + replaceables: [], + rollovers: [], + products: [], + entitlements: [], + prices: [], + free_trials: [], + subscriptions: [], + ...overrides, + }) as SubjectQueryRow; + +describe("mergeEntityAndCustomerSubjectRows", () => { + test("returns entity row unchanged when customer row is missing", () => { + const entityRow = createRow({ + customer_products: [createCustomerProduct({ id: "cp_entity_1" })], + }); + + const merged = mergeEntityAndCustomerSubjectRows({ + entityRow, + customerRow: undefined, + }); + + expect(merged).toBe(entityRow); + }); + + test("drops entity-scoped products belonging to a different customer", () => { + const entityRow = createRow({ + customer_products: [ + createCustomerProduct({ id: "cp_ours" }), + createCustomerProduct({ + id: "cp_other_customer", + internalCustomerId: "cus_internal_other", + }), + ], + }); + const customerRow = createRow(); + + const merged = mergeEntityAndCustomerSubjectRows({ + entityRow, + customerRow, + }); + + expect(merged.customer_products.map((product) => product.id)).toEqual([ + "cp_ours", + ]); + }); + + test("orders entity-scoped rows before customer-level rows", () => { + const entityRow = createRow({ + customer_products: [ + createCustomerProduct({ id: "cp_entity_1" }), + createCustomerProduct({ id: "cp_entity_2" }), + ], + extra_customer_entitlements: [ + createCustomerEntitlement({ + id: "ce_extra_entity", + customerProductId: null, + }), + ], + }); + const customerRow = createRow({ + customer_products: [createCustomerProduct({ id: "cp_customer_1" })], + extra_customer_entitlements: [ + createCustomerEntitlement({ + id: "ce_extra_customer", + customerProductId: null, + }), + ], + }); + + const merged = mergeEntityAndCustomerSubjectRows({ + entityRow, + customerRow, + }); + + expect(merged.customer_products.map((product) => product.id)).toEqual([ + "cp_entity_1", + "cp_entity_2", + "cp_customer_1", + ]); + expect( + merged.extra_customer_entitlements.map((entitlement) => entitlement.id), + ).toEqual(["ce_extra_entity", "ce_extra_customer"]); + }); + + test("keeps customer fields from the entity row", () => { + const entityRow = createRow({ + entity: { internal_id: "entity_internal_1" } as SubjectQueryRow["entity"], + }); + const customerRow = createRow(); + + const merged = mergeEntityAndCustomerSubjectRows({ + entityRow, + customerRow, + }); + + expect(merged.customer).toBe(entityRow.customer); + expect(merged.entity).toBe(entityRow.entity); + expect(merged.invoices).toBeUndefined(); + expect(merged.entity_aggregations).toBeUndefined(); + }); + + test("cap truncation drops customer-level products and all their dependent rows", () => { + const entityProducts = Array.from( + { length: CUSTOMER_PRODUCT_LIMIT - 1 }, + (_, index) => createCustomerProduct({ id: `cp_entity_${index}` }), + ); + const keptProduct = createCustomerProduct({ + id: "cp_customer_kept", + freeTrialId: "ft_kept", + subscriptionIds: ["sub_kept"], + }); + const droppedProduct = createCustomerProduct({ + id: "cp_customer_dropped", + freeTrialId: "ft_dropped", + subscriptionIds: ["sub_dropped"], + }); + + const entityRow = createRow({ customer_products: entityProducts }); + const customerRow = createRow({ + customer_products: [keptProduct, droppedProduct], + customer_entitlements: [ + createCustomerEntitlement({ + id: "ce_kept", + customerProductId: keptProduct.id, + entitlementId: "ent_kept", + }), + createCustomerEntitlement({ + id: "ce_dropped", + customerProductId: droppedProduct.id, + entitlementId: "ent_dropped", + }), + ], + customer_prices: [ + createCustomerPrice({ + id: "cpr_kept", + customerProductId: keptProduct.id, + priceId: "price_kept", + }), + createCustomerPrice({ + id: "cpr_dropped", + customerProductId: droppedProduct.id, + priceId: "price_dropped", + }), + ], + rollovers: [ + { id: "ro_kept", cus_ent_id: "ce_kept" }, + { id: "ro_dropped", cus_ent_id: "ce_dropped" }, + ] as SubjectQueryRow["rollovers"], + replaceables: [ + { id: "rep_kept", cus_ent_id: "ce_kept" }, + { id: "rep_dropped", cus_ent_id: "ce_dropped" }, + ] as SubjectQueryRow["replaceables"], + products: [ + { internal_id: keptProduct.internal_product_id }, + { internal_id: droppedProduct.internal_product_id }, + ] as SubjectQueryRow["products"], + entitlements: [ + { id: "ent_kept" }, + { id: "ent_dropped" }, + ] as SubjectQueryRow["entitlements"], + prices: [ + { id: "price_kept" }, + { id: "price_dropped" }, + ] as SubjectQueryRow["prices"], + free_trials: [ + { id: "ft_kept" }, + { id: "ft_dropped" }, + ] as SubjectQueryRow["free_trials"], + subscriptions: [ + { stripe_id: "sub_kept" }, + { stripe_id: "sub_dropped" }, + ] as SubjectQueryRow["subscriptions"], + }); + + const merged = mergeEntityAndCustomerSubjectRows({ + entityRow, + customerRow, + }); + + expect(merged.customer_products).toHaveLength(CUSTOMER_PRODUCT_LIMIT); + expect( + merged.customer_products[merged.customer_products.length - 1]?.id, + ).toBe(keptProduct.id); + expect(merged.customer_entitlements.map((row) => row.id)).toEqual([ + "ce_kept", + ]); + expect(merged.customer_prices.map((row) => row.id)).toEqual(["cpr_kept"]); + expect(merged.rollovers.map((row) => row.id)).toEqual(["ro_kept"]); + expect(merged.replaceables.map((row) => row.id)).toEqual(["rep_kept"]); + expect(merged.products.map((row) => row.internal_id)).toContain( + keptProduct.internal_product_id, + ); + expect(merged.products.map((row) => row.internal_id)).not.toContain( + droppedProduct.internal_product_id, + ); + expect(merged.entitlements.map((row) => row.id)).toEqual(["ent_kept"]); + expect(merged.prices.map((row) => row.id)).toEqual(["price_kept"]); + expect(merged.free_trials.map((row) => row.id)).toEqual(["ft_kept"]); + expect(merged.subscriptions.map((row) => row.stripe_id)).toEqual([ + "sub_kept", + ]); + }); + + test("extras cap truncation drops the dropped extras' rollovers and entitlement refs", () => { + const entityExtras = Array.from( + { length: EXTRA_CUSTOMER_ENTITLEMENT_LIMIT }, + (_, index) => + createCustomerEntitlement({ + id: `ce_extra_entity_${index}`, + customerProductId: null, + entitlementId: `ent_extra_entity_${index}`, + }), + ); + const droppedExtra = createCustomerEntitlement({ + id: "ce_extra_customer_dropped", + customerProductId: null, + entitlementId: "ent_extra_dropped", + }); + + const entityRow = createRow({ + extra_customer_entitlements: entityExtras, + entitlements: entityExtras.map( + (extra) => + ({ + id: extra.entitlement_id, + }) as SubjectQueryRow["entitlements"][number], + ), + }); + const customerRow = createRow({ + extra_customer_entitlements: [droppedExtra], + rollovers: [ + { id: "ro_dropped", cus_ent_id: droppedExtra.id }, + ] as SubjectQueryRow["rollovers"], + entitlements: [ + { id: droppedExtra.entitlement_id }, + ] as SubjectQueryRow["entitlements"], + }); + + const merged = mergeEntityAndCustomerSubjectRows({ + entityRow, + customerRow, + }); + + expect(merged.extra_customer_entitlements).toHaveLength( + EXTRA_CUSTOMER_ENTITLEMENT_LIMIT, + ); + expect( + merged.extra_customer_entitlements.map((row) => row.id), + ).not.toContain(droppedExtra.id); + expect(merged.rollovers).toHaveLength(0); + expect(merged.entitlements.map((row) => row.id)).not.toContain( + droppedExtra.entitlement_id, + ); + }); + + test("dedupes shared catalog rows and subscriptions across both rows", () => { + const entityProduct = createCustomerProduct({ + id: "cp_entity_1", + internalProductId: "prod_shared", + freeTrialId: "ft_shared", + subscriptionIds: ["sub_shared"], + }); + const customerProduct = createCustomerProduct({ + id: "cp_customer_1", + internalProductId: "prod_shared", + freeTrialId: "ft_shared", + subscriptionIds: ["sub_shared"], + }); + const sharedCatalog = { + products: [{ internal_id: "prod_shared" }] as SubjectQueryRow["products"], + prices: [{ id: "price_shared" }] as SubjectQueryRow["prices"], + entitlements: [{ id: "ent_shared" }] as SubjectQueryRow["entitlements"], + free_trials: [{ id: "ft_shared" }] as SubjectQueryRow["free_trials"], + subscriptions: [ + { stripe_id: "sub_shared" }, + ] as SubjectQueryRow["subscriptions"], + }; + + const entityRow = createRow({ + customer_products: [entityProduct], + customer_entitlements: [ + createCustomerEntitlement({ + id: "ce_entity", + customerProductId: entityProduct.id, + entitlementId: "ent_shared", + }), + ], + customer_prices: [ + createCustomerPrice({ + id: "cpr_entity", + customerProductId: entityProduct.id, + priceId: "price_shared", + }), + ], + ...sharedCatalog, + }); + const customerRow = createRow({ + customer_products: [customerProduct], + customer_entitlements: [ + createCustomerEntitlement({ + id: "ce_customer", + customerProductId: customerProduct.id, + entitlementId: "ent_shared", + }), + ], + customer_prices: [ + createCustomerPrice({ + id: "cpr_customer", + customerProductId: customerProduct.id, + priceId: "price_shared", + }), + ], + ...sharedCatalog, + }); + + const merged = mergeEntityAndCustomerSubjectRows({ + entityRow, + customerRow, + }); + + expect(merged.products).toHaveLength(1); + expect(merged.prices).toHaveLength(1); + expect(merged.entitlements).toHaveLength(1); + expect(merged.free_trials).toHaveLength(1); + expect(merged.subscriptions).toHaveLength(1); + expect(merged.customer_entitlements.map((row) => row.id)).toEqual([ + "ce_entity", + "ce_customer", + ]); + }); + + test("sorts merged catalog rows by id to mirror DISTINCT ON ordering", () => { + const entityProduct = createCustomerProduct({ + id: "cp_entity_1", + internalProductId: "prod_b", + }); + const customerProduct = createCustomerProduct({ + id: "cp_customer_1", + internalProductId: "prod_a", + }); + + const entityRow = createRow({ + customer_products: [entityProduct], + products: [{ internal_id: "prod_b" }] as SubjectQueryRow["products"], + }); + const customerRow = createRow({ + customer_products: [customerProduct], + products: [{ internal_id: "prod_a" }] as SubjectQueryRow["products"], + }); + + const merged = mergeEntityAndCustomerSubjectRows({ + entityRow, + customerRow, + }); + + expect(merged.products.map((row) => row.internal_id)).toEqual([ + "prod_a", + "prod_b", + ]); + }); +}); diff --git a/server/tests/unit/features/get-credit-cost.test.ts b/server/tests/unit/features/get-credit-cost.test.ts index 5f64b31fa..5eb2d6f31 100644 --- a/server/tests/unit/features/get-credit-cost.test.ts +++ b/server/tests/unit/features/get-credit-cost.test.ts @@ -1,17 +1,23 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, mock, test } from "bun:test"; import { ErrCode, type Feature, FeatureType, FeatureUsageType, } from "@autumn/shared"; -import { - getModelCreditCost, - getModelCreditCostBreakdown, -} from "@/internal/features/aiCreditSystemUtils.js"; -import { getCreditCost } from "@/internal/features/creditSystemUtils.js"; -// Uses custom/* models so pricing resolves offline (no models.dev fetch). +mock.module("@/internal/features/utils/getModelPricing.js", () => ({ + getModelsDevPricing: async () => ({}), +})); + +const { getModelCreditCost, getModelCreditCostBreakdown } = await import( + "@/internal/features/aiCreditSystemUtils.js" +); +const { getCreditCost } = await import( + "@/internal/features/creditSystemUtils.js" +); + +// custom/* models price from model_markups; pricing data is mocked empty. const CUSTOM_MODEL = "custom/foo"; const aiCreditFeature: Feature = { diff --git a/server/tests/unit/rate-limits/org-aggregate-config.test.ts b/server/tests/unit/rate-limits/org-aggregate-config.test.ts new file mode 100644 index 000000000..922739dc9 --- /dev/null +++ b/server/tests/unit/rate-limits/org-aggregate-config.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, test } from "bun:test"; +import { + getOrgAggregateType, + RATE_LIMIT_CONFIGS, + RateLimitScope, + RateLimitType, +} from "@/internal/misc/rateLimiter/rateLimitConfigs.js"; + +describe("org aggregate rate limits", () => { + test("high-volume per-customer types map to an org aggregate", () => { + expect(getOrgAggregateType(RateLimitType.Track)).toBe( + RateLimitType.TrackOrg, + ); + expect(getOrgAggregateType(RateLimitType.Check)).toBe( + RateLimitType.CheckOrg, + ); + expect(getOrgAggregateType(RateLimitType.CustomerEntitiesGet)).toBe( + RateLimitType.EntitiesGetOrg, + ); + }); + + test("types without an aggregate return undefined", () => { + expect(getOrgAggregateType(RateLimitType.General)).toBeUndefined(); + expect(getOrgAggregateType(RateLimitType.Attach)).toBeUndefined(); + expect(getOrgAggregateType(RateLimitType.TrackOrg)).toBeUndefined(); + }); + + test("aggregate configs are org-scoped, redis-backed, 60s windows", () => { + const aggregates = [ + RateLimitType.TrackOrg, + RateLimitType.CheckOrg, + RateLimitType.EntitiesGetOrg, + ]; + for (const type of aggregates) { + const config = RATE_LIMIT_CONFIGS[type]; + expect(config.scope).toBe(RateLimitScope.Org); + expect(config.notInRedis).toBe(false); + expect(config.windowMs).toBe(60_000); + expect(config.limit).toBeGreaterThan(0); + } + }); + + test("check/track aggregates degrade (fail open) instead of rejecting", () => { + expect(RATE_LIMIT_CONFIGS[RateLimitType.CheckOrg].overLimit).toBe( + "degrade", + ); + expect(RATE_LIMIT_CONFIGS[RateLimitType.TrackOrg].overLimit).toBe( + "degrade", + ); + expect( + RATE_LIMIT_CONFIGS[RateLimitType.EntitiesGetOrg].overLimit, + ).toBeUndefined(); + }); +}); diff --git a/shared/api/common/paginationConfigs.ts b/shared/api/common/paginationConfigs.ts index 1f0f292e6..e4b0c37b2 100644 --- a/shared/api/common/paginationConfigs.ts +++ b/shared/api/common/paginationConfigs.ts @@ -19,7 +19,7 @@ export const PAGINATION_CONFIGS: Record = { }, [PaginationType.ListEntities]: { defaultLimit: PaginationDefaults.DefaultLimit, - maxLimit: PaginationDefaults.MaxLimit, + maxLimit: PaginationDefaults.SchemaHardCeiling, }, [PaginationType.SearchCustomers]: { defaultLimit: PaginationDefaults.DefaultLimit, @@ -30,5 +30,3 @@ export const PAGINATION_CONFIGS: Record = { maxLimit: PaginationDefaults.MaxLimit, }, }; - - diff --git a/shared/drizzle/0011_easy_spot.sql b/shared/drizzle/0011_easy_spot.sql new file mode 100644 index 000000000..6f6d9a5dc --- /dev/null +++ b/shared/drizzle/0011_easy_spot.sql @@ -0,0 +1 @@ +CREATE INDEX CONCURRENTLY "idx_entities_customer_created_at" ON "entities" USING btree ("internal_customer_id","created_at" DESC,"id" DESC); diff --git a/shared/drizzle/meta/0011_snapshot.json b/shared/drizzle/meta/0011_snapshot.json new file mode 100644 index 000000000..a7185f0c2 --- /dev/null +++ b/shared/drizzle/meta/0011_snapshot.json @@ -0,0 +1,7629 @@ +{ + "id": "42afdc3e-154a-493a-bb50-7ae1bfe59831", + "prevId": "c5275384-0822-47e9-b14d-d23cd14d42cc", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.actions": { + "name": "actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_slug": { + "name": "org_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "properties": { + "name": "properties", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_actions_on_internal_entity_id": { + "name": "idx_actions_on_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "actions_org_id_fkey": { + "name": "actions_org_id_fkey", + "tableFrom": "actions", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "actions_customer_id_fkey": { + "name": "actions_customer_id_fkey", + "tableFrom": "actions", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "actions_entity_id_fkey": { + "name": "actions_entity_id_fkey", + "tableFrom": "actions", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.agent_rules": { + "name": "agent_rules", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_slug": { + "name": "org_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_rules": { + "name": "entity_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "credit_rules": { + "name": "credit_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_rules_org_id_fkey": { + "name": "agent_rules_org_id_fkey", + "tableFrom": "agent_rules", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hashed_key": { + "name": "hashed_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "api_keys_org_id_fkey": { + "name": "api_keys_org_id_fkey", + "tableFrom": "api_keys", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_keys_hashed_key_key": { + "name": "api_keys_hashed_key_key", + "nullsNotDistinct": false, + "columns": [ + "hashed_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auto_topup_limit_states": { + "name": "auto_topup_limit_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purchase_window_ends_at": { + "name": "purchase_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "purchase_count": { + "name": "purchase_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempt_window_ends_at": { + "name": "attempt_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "attempt_count": { + "name": "attempt_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed_attempt_window_ends_at": { + "name": "failed_attempt_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "failed_attempt_count": { + "name": "failed_attempt_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "last_failed_attempt_at": { + "name": "last_failed_attempt_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + } + }, + "indexes": { + "auto_topup_limits_org_env_internal_customer_feature_unique": { + "name": "auto_topup_limits_org_env_internal_customer_feature_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "feature_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auto_topup_limits_org_id_fkey": { + "name": "auto_topup_limits_org_id_fkey", + "tableFrom": "auto_topup_limit_states", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_topup_limits_internal_customer_id_fkey": { + "name": "auto_topup_limits_internal_customer_id_fkey", + "tableFrom": "auto_topup_limit_states", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_approvals": { + "name": "chat_approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_ts": { + "name": "message_ts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_args": { + "name": "tool_args", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "preview": { + "name": "preview", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "decided_at": { + "name": "decided_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "decided_by_provider_user_id": { + "name": "decided_by_provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "chat_approvals_org_id_fkey": { + "name": "chat_approvals_org_id_fkey", + "tableFrom": "chat_approvals", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_installations": { + "name": "chat_installations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_name": { + "name": "workspace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_access_token": { + "name": "bot_access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "default_env": { + "name": "default_env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sandbox_api_key_id": { + "name": "sandbox_api_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sandbox_api_key": { + "name": "sandbox_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "live_api_key_id": { + "name": "live_api_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "live_api_key": { + "name": "live_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_by_user_id": { + "name": "installed_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_by_provider_user_id": { + "name": "installed_by_provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_installations_org_id_fkey": { + "name": "chat_installations_org_id_fkey", + "tableFrom": "chat_installations", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_installations_org_provider_key": { + "name": "chat_installations_org_provider_key", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "provider" + ] + }, + "chat_installations_provider_workspace_key": { + "name": "chat_installations_provider_workspace_key", + "nullsNotDistinct": false, + "columns": [ + "provider", + "workspace_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_oauth_credentials": { + "name": "chat_oauth_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "chat_installation_id": { + "name": "chat_installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_consent_id": { + "name": "oauth_consent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_oauth_credentials_installation_id_fkey": { + "name": "chat_oauth_credentials_installation_id_fkey", + "tableFrom": "chat_oauth_credentials", + "tableTo": "chat_installations", + "columnsFrom": [ + "chat_installation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_oauth_credentials_org_id_fkey": { + "name": "chat_oauth_credentials_org_id_fkey", + "tableFrom": "chat_oauth_credentials", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_oauth_credentials_installation_env_key": { + "name": "chat_oauth_credentials_installation_env_key", + "nullsNotDistinct": false, + "columns": [ + "chat_installation_id", + "env" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_results": { + "name": "chat_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.checkouts": { + "name": "checkouts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "params": { + "name": "params", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "params_version": { + "name": "params_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "response": { + "name": "response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_checkouts_stripe_invoice_id": { + "name": "idx_checkouts_stripe_invoice_id", + "columns": [ + { + "expression": "stripe_invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_entitlements": { + "name": "customer_entitlements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "customer_product_id": { + "name": "customer_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entitlement_id": { + "name": "entitlement_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text COLLATE \"C\"", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "unlimited": { + "name": "unlimited", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "balance": { + "name": "balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "next_reset_at": { + "name": "next_reset_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "usage_allowed": { + "name": "usage_allowed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "adjustment": { + "name": "adjustment", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "additional_balance": { + "name": "additional_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "entities": { + "name": "entities", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "cache_version": { + "name": "cache_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_entitlements_product_id": { + "name": "idx_customer_entitlements_product_id", + "columns": [ + { + "expression": "customer_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_internal_customer_id": { + "name": "idx_customer_entitlements_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hash", + "with": {} + }, + "idx_customer_entitlements_internal_customer_id_btree": { + "name": "idx_customer_entitlements_internal_customer_id_btree", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_entitlement_id": { + "name": "idx_customer_entitlements_entitlement_id", + "columns": [ + { + "expression": "entitlement_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_internal_entity_id": { + "name": "idx_customer_entitlements_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hash", + "with": {} + }, + "idx_customer_entitlements_on_next_reset_at": { + "name": "idx_customer_entitlements_on_next_reset_at", + "columns": [ + { + "expression": "next_reset_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_loose_customer_expires": { + "name": "idx_customer_entitlements_loose_customer_expires", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customer_entitlements\".\"customer_product_id\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entitlements_internal_feature_id_fkey": { + "name": "entitlements_internal_feature_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "features", + "columnsFrom": [ + "internal_feature_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_entitlements_internal_entity_id_fkey": { + "name": "customer_entitlements_internal_entity_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_entitlements_customer_product_id_fkey": { + "name": "customer_entitlements_customer_product_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "customer_products", + "columnsFrom": [ + "customer_product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "customer_entitlements_entitlement_id_fkey": { + "name": "customer_entitlements_entitlement_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "entitlements", + "columnsFrom": [ + "entitlement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_prices": { + "name": "customer_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "price_id": { + "name": "price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_product_id": { + "name": "customer_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_prices_product_id": { + "name": "idx_customer_prices_product_id", + "columns": [ + { + "expression": "customer_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_prices_price_id": { + "name": "idx_customer_prices_price_id", + "columns": [ + { + "expression": "price_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_prices_internal_customer_id": { + "name": "idx_customer_prices_internal_customer_id", + "columns": [ + { + "expression": "\"internal_customer_id\" COLLATE \"C\"", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customer_prices\".\"internal_customer_id\" IS NOT NULL", + "concurrently": true, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customer_prices_customer_product_id_fkey": { + "name": "customer_prices_customer_product_id_fkey", + "tableFrom": "customer_prices", + "tableTo": "customer_products", + "columnsFrom": [ + "customer_product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_prices_internal_customer_id_fkey": { + "name": "customer_prices_internal_customer_id_fkey", + "tableFrom": "customer_prices", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_prices_price_id_fkey": { + "name": "customer_prices_price_id_fkey", + "tableFrom": "customer_prices", + "tableTo": "prices", + "columnsFrom": [ + "price_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_products": { + "name": "customer_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "canceled": { + "name": "canceled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "starts_at": { + "name": "starts_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "access_starts_at": { + "name": "access_starts_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "free_trial_id": { + "name": "free_trial_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trial_ends_at": { + "name": "trial_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "billing_cycle_anchor_resets_at": { + "name": "billing_cycle_anchor_resets_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "collection_method": { + "name": "collection_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'charge_automatically'" + }, + "subscription_ids": { + "name": "subscription_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "scheduled_ids": { + "name": "scheduled_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_version": { + "name": "billing_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_version": { + "name": "api_version", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "api_semver": { + "name": "api_semver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_checkout_session_id": { + "name": "stripe_checkout_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_customer_product_id": { + "name": "previous_customer_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "on_trial_end": { + "name": "on_trial_end", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_products_customer_status": { + "name": "idx_customer_products_customer_status", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_products_on_internal_entity_id": { + "name": "idx_customer_products_on_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_products_on_internal_product_id": { + "name": "idx_customer_products_on_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_products_subscription_ids": { + "name": "idx_customer_products_subscription_ids", + "columns": [ + { + "expression": "subscription_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_customer_products_scheduled_ids": { + "name": "idx_customer_products_scheduled_ids", + "columns": [ + { + "expression": "scheduled_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_customer_products_stripe_checkout_session_id": { + "name": "idx_customer_products_stripe_checkout_session_id", + "columns": [ + { + "expression": "stripe_checkout_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_products_revenuecat_processor": { + "name": "idx_customer_products_revenuecat_processor", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "(\"customer_products\".\"processor\" ->> 'type') = 'revenuecat'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customer_products_free_trial_id_fkey": { + "name": "customer_products_free_trial_id_fkey", + "tableFrom": "customer_products", + "tableTo": "free_trials", + "columnsFrom": [ + "free_trial_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "customer_products_internal_customer_id_fkey": { + "name": "customer_products_internal_customer_id_fkey", + "tableFrom": "customer_products", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "customer_products_internal_product_id_fkey": { + "name": "customer_products_internal_product_id_fkey", + "tableFrom": "customer_products", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "customer_products_internal_entity_id_fkey": { + "name": "customer_products_internal_entity_id_fkey", + "tableFrom": "customer_products", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customers": { + "name": "customers", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "processors": { + "name": "processors", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "send_email_receipts": { + "name": "send_email_receipts", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "auto_topups": { + "name": "auto_topups", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "spend_limits": { + "name": "spend_limits", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_alerts": { + "name": "usage_alerts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "overage_allowed": { + "name": "overage_allowed", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "customers_email_null_id_unique": { + "name": "customers_email_null_id_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"email\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"customers\".\"id\" IS NULL AND \"customers\".\"email\" IS NOT NULL AND \"customers\".\"email\" != ''", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_org_env_fingerprint": { + "name": "idx_customers_org_env_fingerprint", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customers\".\"fingerprint\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_processor_id": { + "name": "idx_customers_processor_id", + "columns": [ + { + "expression": "(\"processor\" ->> 'id')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_composite": { + "name": "idx_customers_composite", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_org_env_internal_id": { + "name": "idx_customers_org_env_internal_id", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"internal_id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_email_trgm": { + "name": "idx_customers_email_trgm", + "columns": [ + { + "expression": "\"email\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customers\".\"email\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_customers_name_trgm": { + "name": "idx_customers_name_trgm", + "columns": [ + { + "expression": "\"name\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customers\".\"name\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_customers_id_trgm": { + "name": "idx_customers_id_trgm", + "columns": [ + { + "expression": "\"id\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customers\".\"id\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_customers_org_id_env_created_at": { + "name": "idx_customers_org_id_env_created_at", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_cursor": { + "name": "idx_customers_cursor", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_processors_revenuecat": { + "name": "idx_customers_processors_revenuecat", + "columns": [ + { + "expression": "(\"processors\" ->> 'revenuecat')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_processors_vercel": { + "name": "idx_customers_processors_vercel", + "columns": [ + { + "expression": "(\"processors\" ->> 'vercel')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customers_org_id_fkey": { + "name": "customers_org_id_fkey", + "tableFrom": "customers", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "cus_id_constraint": { + "name": "cus_id_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "id", + "env" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.entities": { + "name": "entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "spend_limits": { + "name": "spend_limits", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_alerts": { + "name": "usage_alerts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "overage_allowed": { + "name": "overage_allowed", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_entities_internal_customer_id": { + "name": "idx_entities_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entities_customer_internal_desc": { + "name": "idx_entities_customer_internal_desc", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"internal_id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entities_org_env_id": { + "name": "idx_entities_org_env_id", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entities_cursor": { + "name": "idx_entities_cursor", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entities_customer_created_at": { + "name": "idx_entities_customer_created_at", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entities_internal_customer_id_fkey": { + "name": "entities_internal_customer_id_fkey", + "tableFrom": "entities", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entities_internal_feature_id_fkey": { + "name": "entities_internal_feature_id_fkey", + "tableFrom": "entities", + "tableTo": "features", + "columnsFrom": [ + "internal_feature_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entities_org_id_fkey": { + "name": "entities_org_id_fkey", + "tableFrom": "entities", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entity_id_constraint": { + "name": "entity_id_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "env", + "internal_customer_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entitlements": { + "name": "entitlements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_reward_id": { + "name": "internal_reward_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "allowance_type": { + "name": "allowance_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowance": { + "name": "allowance", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "interval": { + "name": "interval", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "interval_count": { + "name": "interval_count", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "carry_from_previous": { + "name": "carry_from_previous", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entity_feature_id": { + "name": "entity_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_limit": { + "name": "usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "expiry_duration": { + "name": "expiry_duration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_length": { + "name": "expiry_length", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "rollover": { + "name": "rollover", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_entitlements_internal_product_id": { + "name": "idx_entitlements_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entitlements_internal_reward_id": { + "name": "idx_entitlements_internal_reward_id", + "columns": [ + { + "expression": "internal_reward_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entitlements_reward_feature": { + "name": "idx_entitlements_reward_feature", + "columns": [ + { + "expression": "internal_reward_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_feature_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entitlements_internal_reward_id_c_partial": { + "name": "idx_entitlements_internal_reward_id_c_partial", + "columns": [ + { + "expression": "\"internal_reward_id\" COLLATE \"C\"", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"entitlements\".\"internal_reward_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entitlements_internal_feature_id_fkey": { + "name": "entitlements_internal_feature_id_fkey", + "tableFrom": "entitlements", + "tableTo": "features", + "columnsFrom": [ + "internal_feature_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entitlements_internal_product_id_fkey": { + "name": "entitlements_internal_product_id_fkey", + "tableFrom": "entitlements", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "entitlements_internal_reward_id_fkey": { + "name": "entitlements_internal_reward_id_fkey", + "tableFrom": "entitlements", + "tableTo": "rewards", + "columnsFrom": [ + "internal_reward_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entitlements_id_key": { + "name": "entitlements_id_key", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_slug": { + "name": "org_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "event_name": { + "name": "event_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "value": { + "name": "value", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "set_usage": { + "name": "set_usage", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "properties": { + "name": "properties", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "deductions": { + "name": "deductions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_events_internal_customer_id": { + "name": "idx_events_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_events_internal_entity_id": { + "name": "idx_events_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_events_customer_non_usage_ts": { + "name": "idx_events_customer_non_usage_ts", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"timestamp\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"events\".\"set_usage\" = false", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "events_internal_customer_id_fkey": { + "name": "events_internal_customer_id_fkey", + "tableFrom": "events", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_event_constraint": { + "name": "unique_event_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "env", + "customer_id", + "event_name", + "idempotency_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.features": { + "name": "features", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "display": { + "name": "display", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "event_names": { + "name": "event_names", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": { + "features_org_id_fkey": { + "name": "features_org_id_fkey", + "tableFrom": "features", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "feature_id_constraint": { + "name": "feature_id_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "id", + "env" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.free_trials": { + "name": "free_trials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'day'" + }, + "length": { + "name": "length", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "unique_fingerprint": { + "name": "unique_fingerprint", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "card_required": { + "name": "card_required", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "on_end": { + "name": "on_end", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_free_trials_internal_product_id": { + "name": "idx_free_trials_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "free_trials_internal_product_id_fkey": { + "name": "free_trials_internal_product_id_fkey", + "tableFrom": "free_trials", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitation_organizationId_idx": { + "name": "invitation_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_organization_id_organizations_id_fk": { + "name": "invitation_organization_id_organizations_id_fk", + "tableFrom": "invitation", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.invoice_line_items": { + "name": "invoice_line_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "invoice_id": { + "name": "invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_item_id": { + "name": "stripe_invoice_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_item_id": { + "name": "stripe_subscription_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_product_id": { + "name": "stripe_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_price_id": { + "name": "stripe_price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_discountable": { + "name": "stripe_discountable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "amount": { + "name": "amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "amount_after_discounts": { + "name": "amount_after_discounts", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'usd'" + }, + "stripe_quantity": { + "name": "stripe_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "total_quantity": { + "name": "total_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "paid_quantity": { + "name": "paid_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_source": { + "name": "description_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "billing_timing": { + "name": "billing_timing", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prorated": { + "name": "prorated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "price_id": { + "name": "price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_product_ids": { + "name": "customer_product_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "customer_price_ids": { + "name": "customer_price_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "customer_entitlement_ids": { + "name": "customer_entitlement_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effective_period_start": { + "name": "effective_period_start", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "effective_period_end": { + "name": "effective_period_end", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "discounts": { + "name": "discounts", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": { + "idx_invoice_line_items_customer_product_ids": { + "name": "idx_invoice_line_items_customer_product_ids", + "columns": [ + { + "expression": "customer_product_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "invoice_line_items_invoice_id_fkey": { + "name": "invoice_line_items_invoice_id_fkey", + "tableFrom": "invoice_line_items", + "tableTo": "invoices", + "columnsFrom": [ + "invoice_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invoice_line_items_stripe_id_unique": { + "name": "invoice_line_items_stripe_id_unique", + "nullsNotDistinct": false, + "columns": [ + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invoice_templates": { + "name": "invoice_templates", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "footer": { + "name": "footer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "net_terms_days": { + "name": "net_terms_days", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_invoice_templates_org_id": { + "name": "idx_invoice_templates_org_id", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invoice_templates_org_id_fkey": { + "name": "invoice_templates_org_id_fkey", + "tableFrom": "invoice_templates", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invoice_templates_id_unique": { + "name": "invoice_templates_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invoices": { + "name": "invoices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "product_ids": { + "name": "product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "internal_product_ids": { + "name": "internal_product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "processor_type": { + "name": "processor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "hosted_invoice_url": { + "name": "hosted_invoice_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total": { + "name": "total", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "amount_paid": { + "name": "amount_paid", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "refunded_amount": { + "name": "refunded_amount", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'usd'" + }, + "discounts": { + "name": "discounts", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "items": { + "name": "items", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": { + "idx_invoices_customer_created": { + "name": "idx_invoices_customer_created", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_invoices_internal_entity_id": { + "name": "idx_invoices_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"invoices\".\"internal_entity_id\" IS NOT NULL", + "concurrently": true, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invoices_internal_customer_id_fkey": { + "name": "invoices_internal_customer_id_fkey", + "tableFrom": "invoices", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invoices_internal_entity_id_fkey": { + "name": "invoices_internal_entity_id_fkey", + "tableFrom": "invoices", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invoices_stripe_id_key": { + "name": "invoices_stripe_id_key", + "nullsNotDistinct": false, + "columns": [ + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.jwks": { + "name": "jwks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "member_organizationId_idx": { + "name": "member_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_userId_idx": { + "name": "member_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_organization_id_organizations_id_fk": { + "name": "member_organization_id_organizations_id_fk", + "tableFrom": "member", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.metadata": { + "name": "metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_checkout_session_id": { + "name": "stripe_checkout_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_errors": { + "name": "migration_errors", + "schema": "", + "columns": { + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_job_id": { + "name": "migration_job_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "migration_customers_internal_customer_id_fkey": { + "name": "migration_customers_internal_customer_id_fkey", + "tableFrom": "migration_errors", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_customers_migration_job_id_fkey": { + "name": "migration_customers_migration_job_id_fkey", + "tableFrom": "migration_errors", + "tableTo": "migration_jobs", + "columnsFrom": [ + "migration_job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "migration_errors_pkey": { + "name": "migration_errors_pkey", + "columns": [ + "internal_customer_id", + "migration_job_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_item_runs": { + "name": "migration_item_runs", + "schema": "", + "columns": { + "migration_item_run_id": { + "name": "migration_item_run_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "migration_internal_id": { + "name": "migration_internal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_run_id": { + "name": "migration_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "item_kind": { + "name": "item_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "migration_item_runs_live_unique": { + "name": "migration_item_runs_live_unique", + "columns": [ + { + "expression": "migration_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"migration_item_runs\".\"dry_run\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_item_runs_dry_run_unique": { + "name": "migration_item_runs_dry_run_unique", + "columns": [ + { + "expression": "migration_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"migration_item_runs\".\"dry_run\" = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_item_runs_customer_recent_idx": { + "name": "migration_item_runs_customer_recent_idx", + "columns": [ + { + "expression": "item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"migration_item_runs\".\"item_kind\" = 'customer'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_jobs": { + "name": "migration_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "current_step": { + "name": "current_step", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "from_internal_product_id": { + "name": "from_internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "to_internal_product_id": { + "name": "to_internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "step_details": { + "name": "step_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "migration_jobs_from_internal_product_id_fkey": { + "name": "migration_jobs_from_internal_product_id_fkey", + "tableFrom": "migration_jobs", + "tableTo": "products", + "columnsFrom": [ + "from_internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_jobs_org_id_fkey": { + "name": "migration_jobs_org_id_fkey", + "tableFrom": "migration_jobs", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_jobs_to_internal_product_id_fkey": { + "name": "migration_jobs_to_internal_product_id_fkey", + "tableFrom": "migration_jobs", + "tableTo": "products", + "columnsFrom": [ + "to_internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_runs": { + "name": "migration_runs", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "migration_internal_id": { + "name": "migration_internal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "lazy_run": { + "name": "lazy_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_run_id": { + "name": "trigger_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "only_ids": { + "name": "only_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "target_limit": { + "name": "target_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "migration_runs_active_per_migration_unique": { + "name": "migration_runs_active_per_migration_unique", + "columns": [ + { + "expression": "migration_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"migration_runs\".\"status\" IN ('queued', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "migration_runs_migration_internal_id_fkey": { + "name": "migration_runs_migration_internal_id_fkey", + "tableFrom": "migration_runs", + "tableTo": "migrations", + "columnsFrom": [ + "migration_internal_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_runs_org_id_fkey": { + "name": "migration_runs_org_id_fkey", + "tableFrom": "migration_runs", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migrations": { + "name": "migrations", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filter": { + "name": "filter", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "operations": { + "name": "operations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "prepared_state": { + "name": "prepared_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "no_billing_changes": { + "name": "no_billing_changes", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "retry_failed": { + "name": "retry_failed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "migrations_org_env_id_unique": { + "name": "migrations_org_env_id_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "migrations_org_id_fkey": { + "name": "migrations_org_id_fkey", + "tableFrom": "migrations", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_access_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_session_id_session_id_fk": { + "name": "oauth_access_token_session_id_session_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { + "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_refresh_token", + "columnsFrom": [ + "refresh_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_token_unique": { + "name": "oauth_access_token_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_client_user_id_user_id_fk": { + "name": "oauth_client_user_id_user_id_fk", + "tableFrom": "oauth_client", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "nullsNotDistinct": false, + "columns": [ + "client_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_api_key_id": { + "name": "oauth_api_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_consent_client_id_oauth_client_client_id_fk": { + "name": "oauth_consent_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_refresh_token": { + "name": "oauth_refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked": { + "name": "revoked", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auth_time": { + "name": "auth_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_refresh_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_token_session_id_session_id_fk": { + "name": "oauth_refresh_token_session_id_session_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_refresh_token_user_id_user_id_fk": { + "name": "oauth_refresh_token_user_id_user_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_currency": { + "name": "default_currency", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'usd'" + }, + "stripe_connected": { + "name": "stripe_connected", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "stripe_config": { + "name": "stripe_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "test_stripe_connect": { + "name": "test_stripe_connect", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "live_stripe_connect": { + "name": "live_stripe_connect", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "processor_configs": { + "name": "processor_configs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "test_pkey": { + "name": "test_pkey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "live_pkey": { + "name": "live_pkey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "svix_config": { + "name": "svix_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "onboarded": { + "name": "onboarded", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "deployed": { + "name": "deployed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "redis_config": { + "name": "redis_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_organizations_name_trgm": { + "name": "idx_organizations_name_trgm", + "columns": [ + { + "expression": "\"name\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"organizations\".\"name\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_organizations_slug_trgm": { + "name": "idx_organizations_slug_trgm", + "columns": [ + { + "expression": "\"slug\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"organizations\".\"slug\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_organizations_created_at_id": { + "name": "idx_organizations_created_at_id", + "columns": [ + { + "expression": "\"createdAt\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + }, + "organizations_test_pkey_key": { + "name": "organizations_test_pkey_key", + "nullsNotDistinct": false, + "columns": [ + "test_pkey" + ] + }, + "organizations_live_pkey_key": { + "name": "organizations_live_pkey_key", + "nullsNotDistinct": false, + "columns": [ + "live_pkey" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.passkey": { + "name": "passkey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backed_up": { + "name": "backed_up", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "aaguid": { + "name": "aaguid", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "passkey_userId_idx": { + "name": "passkey_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "passkey_credentialId_idx": { + "name": "passkey_credentialId_idx", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "passkey_user_id_user_id_fk": { + "name": "passkey_user_id_user_id_fk", + "tableFrom": "passkey", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "passkey_credential_id_unique": { + "name": "passkey_credential_id_unique", + "nullsNotDistinct": false, + "columns": [ + "credential_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.prices": { + "name": "prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tier_behavior": { + "name": "tier_behavior", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entitlement_id": { + "name": "entitlement_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "proration_config": { + "name": "proration_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + } + }, + "indexes": { + "idx_prices_internal_product_id": { + "name": "idx_prices_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_prices_entitlement_id": { + "name": "idx_prices_entitlement_id", + "columns": [ + { + "expression": "entitlement_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "prices_entitlement_id_fkey": { + "name": "prices_entitlement_id_fkey", + "tableFrom": "prices", + "tableTo": "entitlements", + "columnsFrom": [ + "entitlement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "prices_internal_product_id_fkey": { + "name": "prices_internal_product_id_fkey", + "tableFrom": "prices", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "prices_id_key": { + "name": "prices_id_key", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.products": { + "name": "products", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_add_on": { + "name": "is_add_on", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "group": { + "name": "group", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "version": { + "name": "version", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "base_variant_id": { + "name": "base_variant_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "idx_products_org_env_id_version": { + "name": "idx_products_org_env_id_version", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "products_org_id_fkey": { + "name": "products_org_id_fkey", + "tableFrom": "products", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_product": { + "name": "unique_product", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "id", + "env", + "version" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_codes": { + "name": "referral_codes", + "schema": "", + "columns": { + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text COLLATE \"C\"", + "primaryKey": false, + "notNull": false + }, + "internal_reward_program_id": { + "name": "internal_reward_program_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_referral_codes_internal_customer_id": { + "name": "idx_referral_codes_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "referral_codes_internal_customer_id_fkey": { + "name": "referral_codes_internal_customer_id_fkey", + "tableFrom": "referral_codes", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_codes_internal_reward_program_id_fkey": { + "name": "referral_codes_internal_reward_program_id_fkey", + "tableFrom": "referral_codes", + "tableTo": "reward_programs", + "columnsFrom": [ + "internal_reward_program_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_codes_org_id_fkey": { + "name": "referral_codes_org_id_fkey", + "tableFrom": "referral_codes", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "referral_codes_pkey": { + "name": "referral_codes_pkey", + "columns": [ + "code", + "org_id", + "env" + ] + } + }, + "uniqueConstraints": { + "referral_codes_id_key": { + "name": "referral_codes_id_key", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.replaceables": { + "name": "replaceables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cus_ent_id": { + "name": "cus_ent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "from_entity_id": { + "name": "from_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "delete_next_cycle": { + "name": "delete_next_cycle", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "idx_replaceables_cus_ent_id": { + "name": "idx_replaceables_cus_ent_id", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "replaceables_cus_ent_id_fkey": { + "name": "replaceables_cus_ent_id_fkey", + "tableFrom": "replaceables", + "tableTo": "customer_entitlements", + "columnsFrom": [ + "cus_ent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.revenuecat_mappings": { + "name": "revenuecat_mappings", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autumn_product_id": { + "name": "autumn_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revenuecat_product_ids": { + "name": "revenuecat_product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": { + "revenuecat_mappings_org_id_fkey": { + "name": "revenuecat_mappings_org_id_fkey", + "tableFrom": "revenuecat_mappings", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "revenuecat_mappings_pkey": { + "name": "revenuecat_mappings_pkey", + "columns": [ + "org_id", + "env", + "autumn_product_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reward_programs": { + "name": "reward_programs", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "internal_reward_id": { + "name": "internal_reward_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_redemptions": { + "name": "max_redemptions", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "unlimited_redemptions": { + "name": "unlimited_redemptions", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "when": { + "name": "when", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'immediately'" + }, + "product_ids": { + "name": "product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{\"\"}'" + }, + "exclude_trial": { + "name": "exclude_trial", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "received_by": { + "name": "received_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "reward_triggers_internal_reward_id_fkey": { + "name": "reward_triggers_internal_reward_id_fkey", + "tableFrom": "reward_programs", + "tableTo": "rewards", + "columnsFrom": [ + "internal_reward_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reward_triggers_org_id_fkey": { + "name": "reward_triggers_org_id_fkey", + "tableFrom": "reward_programs", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reward_redemptions": { + "name": "reward_redemptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text COLLATE \"C\"", + "primaryKey": false, + "notNull": false + }, + "triggered": { + "name": "triggered", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "internal_reward_program_id": { + "name": "internal_reward_program_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applied": { + "name": "applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "redeemer_applied": { + "name": "redeemer_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "referral_code_id": { + "name": "referral_code_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reward_internal_id": { + "name": "reward_internal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "promo_code": { + "name": "promo_code", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_reward_redemptions_referral_code_id": { + "name": "idx_reward_redemptions_referral_code_id", + "columns": [ + { + "expression": "referral_code_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_reward_redemptions_reward_internal_id": { + "name": "idx_reward_redemptions_reward_internal_id", + "columns": [ + { + "expression": "reward_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_reward_redemptions_customer_reward": { + "name": "idx_reward_redemptions_customer_reward", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reward_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reward_redemptions_internal_customer_id_fkey": { + "name": "reward_redemptions_internal_customer_id_fkey", + "tableFrom": "reward_redemptions", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reward_redemptions_internal_reward_program_id_fkey": { + "name": "reward_redemptions_internal_reward_program_id_fkey", + "tableFrom": "reward_redemptions", + "tableTo": "reward_programs", + "columnsFrom": [ + "internal_reward_program_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reward_redemptions_referral_code_id_fkey": { + "name": "reward_redemptions_referral_code_id_fkey", + "tableFrom": "reward_redemptions", + "tableTo": "referral_codes", + "columnsFrom": [ + "referral_code_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rewards": { + "name": "rewards", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discount_config": { + "name": "discount_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "free_product_config": { + "name": "free_product_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "free_product_id": { + "name": "free_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "promo_codes": { + "name": "promo_codes", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "coupons_org_id_fkey": { + "name": "coupons_org_id_fkey", + "tableFrom": "rewards", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rollovers": { + "name": "rollovers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cus_ent_id": { + "name": "cus_ent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "balance": { + "name": "balance", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "usage": { + "name": "usage", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "entities": { + "name": "entities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "idx_rollovers_cus_ent_id": { + "name": "idx_rollovers_cus_ent_id", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_rollovers_cus_ent_expires": { + "name": "idx_rollovers_cus_ent_expires", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "rollover_cus_ent_id_fkey": { + "name": "rollover_cus_ent_id_fkey", + "tableFrom": "rollovers", + "tableTo": "customer_entitlements", + "columnsFrom": [ + "cus_ent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.phases": { + "name": "phases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "customer_product_ids": { + "name": "customer_product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "phases_schedule_id_fkey": { + "name": "phases_schedule_id_fkey", + "tableFrom": "phases", + "tableTo": "schedules", + "columnsFrom": [ + "schedule_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "phases_schedule_id_starts_at_key": { + "name": "phases_schedule_id_starts_at_key", + "nullsNotDistinct": false, + "columns": [ + "schedule_id", + "starts_at" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schedules": { + "name": "schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "schedules_customer_scope_unique": { + "name": "schedules_customer_scope_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"schedules\".\"internal_entity_id\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "schedules_entity_scope_unique": { + "name": "schedules_entity_scope_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"schedules\".\"internal_entity_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedules_internal_customer_id": { + "name": "idx_schedules_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedules_internal_entity_id": { + "name": "idx_schedules_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "schedules_org_id_fkey": { + "name": "schedules_org_id_fkey", + "tableFrom": "schedules", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedules_internal_customer_id_fkey": { + "name": "schedules_internal_customer_id_fkey", + "tableFrom": "schedules", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedules_internal_entity_id_fkey": { + "name": "schedules_internal_entity_id_fkey", + "tableFrom": "schedules", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.subscriptions": { + "name": "subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "usage_features": { + "name": "usage_features", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "current_period_start": { + "name": "current_period_start", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "current_period_end": { + "name": "current_period_end", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "subscriptions_org_id_fkey": { + "name": "subscriptions_org_id_fkey", + "tableFrom": "subscriptions", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "subscriptions_stripe_id_key": { + "name": "subscriptions_stripe_id_key", + "nullsNotDistinct": false, + "columns": [ + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_user_name_trgm": { + "name": "idx_user_name_trgm", + "columns": [ + { + "expression": "\"name\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user\".\"name\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_user_email_trgm": { + "name": "idx_user_email_trgm", + "columns": [ + { + "expression": "\"email\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user\".\"email\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_user_created_at_id": { + "name": "idx_user_created_at_id", + "columns": [ + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_created_by_fkey": { + "name": "user_created_by_fkey", + "tableFrom": "user", + "tableTo": "organizations", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vercel_resources": { + "name": "vercel_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "vercel_resources_installation_name_unique_idx": { + "name": "vercel_resources_installation_name_unique_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status <> 'uninstalled'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "vercel_resources_org_id_fkey": { + "name": "vercel_resources_org_id_fkey", + "tableFrom": "vercel_resources", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/shared/drizzle/meta/_journal.json b/shared/drizzle/meta/_journal.json index 08f5ab85f..412f5d07b 100644 --- a/shared/drizzle/meta/_journal.json +++ b/shared/drizzle/meta/_journal.json @@ -78,6 +78,13 @@ "when": 1780990532501, "tag": "0010_magenta_misty_knight", "breakpoints": true + }, + { + "idx": 11, + "version": "7", + "when": 1781085888296, + "tag": "0011_easy_spot", + "breakpoints": true } ] } \ No newline at end of file diff --git a/shared/models/cusModels/entityModels/entityTable.ts b/shared/models/cusModels/entityModels/entityTable.ts index a762395ee..2759483a2 100644 --- a/shared/models/cusModels/entityModels/entityTable.ts +++ b/shared/models/cusModels/entityModels/entityTable.ts @@ -76,5 +76,10 @@ export const entities = pgTable( sql`${table.created_at} DESC`, sql`${table.id} DESC`, ), + index("idx_entities_customer_created_at").on( + table.internal_customer_id, + sql`${table.created_at} DESC`, + sql`${table.id} DESC`, + ), ], );