From f55ba7f0603523e96c98e8b3e8441b849d51d4e3 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Thu, 15 May 2025 20:50:32 +0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/db/schema/consumer.ts | 16 ++++++++++ apps/api/src/db/schema/log-entry.ts | 45 +++++++++++++++-------------- apps/api/src/db/schema/utils.ts | 8 +++++ apps/api/src/db/types.test.ts | 17 +++++++++-- apps/api/src/server.ts | 4 +-- 5 files changed, 65 insertions(+), 25 deletions(-) diff --git a/apps/api/src/db/schema/consumer.ts b/apps/api/src/db/schema/consumer.ts index 8e357834..11ec9c4e 100644 --- a/apps/api/src/db/schema/consumer.ts +++ b/apps/api/src/db/schema/consumer.ts @@ -26,6 +26,16 @@ import { // This may require a separate model to aggregate User Applications. // https://docs.rapidapi.com/docs/keys#section-different-api-keys-per-application +/** + * A `Consumer` is a user who has subscribed to a `Project`. + * + * Consumers are used to track usage and billing for a project. + * + * Consumers are linked to a corresponding Stripe Customer. The Stripe customer + * will either be the user's default Stripe Customer for the platform account, + * or a customer on the project's connected Stripe account if the project has + * Stripe Connect enabled. + */ export const consumers = pgTable( 'consumers', { @@ -34,9 +44,15 @@ export const consumers = pgTable( // API token for this consumer token: text().notNull(), + + // The stripe subscription plan this consumer is subscribed to (or 'free' if supported) plan: text(), + // Whether the consumer has made at least one successful API call after + // initializing their subscription. activated: boolean().default(false).notNull(), + + // Whether the consumer's subscription is currently active enabled: boolean().default(true).notNull(), env: text().default('dev').notNull(), diff --git a/apps/api/src/db/schema/log-entry.ts b/apps/api/src/db/schema/log-entry.ts index 06e1060a..b2ea82e1 100644 --- a/apps/api/src/db/schema/log-entry.ts +++ b/apps/api/src/db/schema/log-entry.ts @@ -1,5 +1,5 @@ import { relations } from '@fisch0920/drizzle-orm' -import { index, pgTable, text } from '@fisch0920/drizzle-orm/pg-core' +import { index, jsonb, pgTable, text } from '@fisch0920/drizzle-orm/pg-core' import { z } from '@hono/zod-openapi' import { consumers, consumerSelectSchema } from './consumer' @@ -12,45 +12,48 @@ import { cuid, deploymentId, id, + logEntryLevelEnum, + logEntryTypeEnum, projectId, - stripeId, timestamps } from './utils' +/** + * A `LogEntry` is an internal audit log entry. + */ export const logEntries = pgTable( 'log_entries', { id, ...timestamps, - type: text().notNull(), - level: text().notNull().default('info'), // TODO: enum + // core data (required) + type: logEntryTypeEnum().notNull().default('log'), + level: logEntryLevelEnum().notNull().default('info'), + message: text().notNull(), - // relations + // context info (required) + environment: text(), + service: text(), + requestId: text(), + traceId: text(), + + // relations (optional) userId: cuid(), projectId: projectId(), deploymentId: deploymentId(), consumerId: cuid(), - // (optional) misc context info - service: text(), - hostname: text(), - provider: text(), - ip: text(), - plan: text(), - subtype: text(), - - // (optional) denormalized info - username: text(), - email: text(), - token: text(), - - // (optional) denormalized stripe info - stripeCustomer: stripeId(), - stripeSubscription: stripeId() + // misc metadata (optional) + metadata: jsonb().$type>().default({}).notNull() }, (table) => [ index('log_entry_type_idx').on(table.type), + index('log_entry_level_idx').on(table.level), + index('log_entry_environment_idx').on(table.environment), + index('log_entry_service_idx').on(table.service), + index('log_entry_requestId_idx').on(table.requestId), + index('log_entry_traceId_idx').on(table.traceId), index('log_entry_userId_idx').on(table.userId), index('log_entry_projectId_idx').on(table.projectId), index('log_entry_deploymentId_idx').on(table.deploymentId), diff --git a/apps/api/src/db/schema/utils.ts b/apps/api/src/db/schema/utils.ts index a6e924ba..5d7e2a69 100644 --- a/apps/api/src/db/schema/utils.ts +++ b/apps/api/src/db/schema/utils.ts @@ -92,6 +92,14 @@ export const timestamps = { export const userRoleEnum = pgEnum('UserRole', ['user', 'admin']) export const teamMemberRoleEnum = pgEnum('TeamMemberRole', ['user', 'admin']) +export const logEntryTypeEnum = pgEnum('LogEntryType', ['log']) +export const logEntryLevelEnum = pgEnum('LogEntryLevel', [ + 'trace', + 'debug', + 'info', + 'warn', + 'error' +]) export const { createInsertSchema, createSelectSchema, createUpdateSchema } = createSchemaFactory({ diff --git a/apps/api/src/db/types.test.ts b/apps/api/src/db/types.test.ts index c90fa214..3eeae18c 100644 --- a/apps/api/src/db/types.test.ts +++ b/apps/api/src/db/types.test.ts @@ -1,11 +1,24 @@ import { expectTypeOf, test } from 'vitest' -import type { RawUser, User } from './types' +import type { LogLevel } from '@/lib/logger' -type UserKeys = Exclude +import type { LogEntry, RawLogEntry, RawUser, User } from './types' + +type UserKeys = Exclude +type LogEntryKeys = keyof RawLogEntry & keyof LogEntry test('User types are compatible', () => { expectTypeOf().toExtend() expectTypeOf().toEqualTypeOf() }) + +test('LogEntry types are compatible', () => { + expectTypeOf().toExtend() + + expectTypeOf().toEqualTypeOf< + RawLogEntry[LogEntryKeys] + >() + + expectTypeOf().toEqualTypeOf() +}) diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index 9233029a..11888560 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -34,7 +34,7 @@ const server = serve({ port: env.PORT }) +initExitHooks({ server }) + // eslint-disable-next-line no-console console.log(`Server running on port ${env.PORT}`) - -initExitHooks({ server })