/** * @fileoverview Tenant and API key persistence. * @module store/tenants */ import { getStore, persist } from './db.js'; import { generateApiKey } from '../lib/tokens.js'; import { PLAN_LIMITS } from '../config.js'; import { createDebugger } from '../debug/logger.js'; const log = createDebugger('billing'); /** * @typedef {Object} Tenant * @property {string} id * @property {string} name * @property {string} plan * @property {string} veraeUsername * @property {string} veraePassword * @property {object|null} contract * @property {object} [metadata] * @property {string} createdAt */ /** * @param {string} tenantId * @returns {Tenant|null} */ export function getTenant(tenantId) { return getStore().tenants[tenantId] ?? null; } /** * @param {string} apiKey * @returns {Tenant|null} */ export function getTenantByApiKey(apiKey) { const store = getStore(); const tenantId = store.apiKeys[apiKey]; return tenantId ? store.tenants[tenantId] ?? null : null; } /** * @returns {Tenant[]} */ export function listTenants() { return Object.values(getStore().tenants); } /** * @param {Tenant} tenant * @returns {Tenant} */ export function upsertTenant(tenant) { const store = getStore(); store.tenants[tenant.id] = tenant; persist(); log.debug('tenant upserted', { tenantId: tenant.id, plan: tenant.plan }); return tenant; } /** * Create a tenant and bind a new API key. * * @param {object} params * @param {string} params.id * @param {string} params.name * @param {string} [params.plan='free'] * @param {string} params.veraeUsername * @param {string} params.veraePassword * @param {object|null} [params.contract=null] * @param {string} [params.apiKey] * @param {object} [params.metadata] * @returns {{ tenant: Tenant, apiKey: string }} */ export function createTenant({ id, name, plan = 'free', veraeUsername, veraePassword, contract = null, apiKey = generateApiKey(), metadata = {}, }) { const store = getStore(); /** @type {Tenant} */ const tenant = { id, name, plan, veraeUsername, veraePassword, contract, metadata, createdAt: new Date().toISOString(), }; store.tenants[id] = tenant; store.apiKeys[apiKey] = id; persist(); log.info('tenant created', { tenantId: id, plan, audience: metadata.audience }); return { tenant, apiKey }; } /** * Resolve effective limits for a tenant (plan defaults or enterprise contract). * * @param {Tenant} tenant * @returns {{ * timestamps: number|null, * verifications: number|null, * batch: boolean, * batchMaxItems: number|null, * requestsPerMinute: number, * allowOverage: boolean, * overageRates: object * }} */ export function resolveLimits(tenant) { if (tenant.plan === 'enterprise' && tenant.contract) { return { timestamps: tenant.contract.includedTimestamps ?? null, verifications: tenant.contract.includedVerifications ?? null, batch: tenant.contract.batch !== false, batchMaxItems: tenant.contract.batchMaxItems ?? null, requestsPerMinute: tenant.contract.requestsPerMinute ?? PLAN_LIMITS.enterprise.requestsPerMinute, allowOverage: tenant.contract.allowOverage ?? false, overageRates: tenant.contract.overageRates ?? {}, }; } const base = PLAN_LIMITS[tenant.plan] ?? PLAN_LIMITS.free; return { ...base, allowOverage: false, overageRates: {} }; }