diff --git a/apps/api/package.json b/apps/api/package.json index ce9a229b..074846cf 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -47,6 +47,7 @@ "@paralleldrive/cuid2": "^2.2.2", "@sentry/node": "^9.14.0", "@workos-inc/node": "^7.47.0", + "bcryptjs": "^3.0.2", "eventid": "^2.0.1", "exit-hook": "catalog:", "hono": "^4.7.7", diff --git a/apps/api/src/db/schema/consumer.ts b/apps/api/src/db/schema/consumer.ts index 58e164e4..0a997a15 100644 --- a/apps/api/src/db/schema/consumer.ts +++ b/apps/api/src/db/schema/consumer.ts @@ -107,12 +107,18 @@ export const consumersRelations = relations(consumers, ({ one }) => ({ })) const stripeValidSubscriptionStatuses = new Set([ - 'trialing', 'active', + 'trialing', 'incomplete', 'past_due' ]) +export const consumerSelectSchema = createSelectSchema(consumers) + .openapi('Consumer') + .omit({ + _stripeCustomerId: true + }) + export const consumerInsertSchema = createInsertSchema(consumers).pick({ token: true, plan: true, @@ -124,16 +130,14 @@ export const consumerInsertSchema = createInsertSchema(consumers).pick({ deploymentId: true }) -export const consumerSelectSchema = - createSelectSchema(consumers).openapi('Consumer') - export const consumerUpdateSchema = createUpdateSchema(consumers).refine( - (data) => { + (consumer) => { return { - ...data, + ...consumer, enabled: - data.plan === 'free' || - (data.status && stripeValidSubscriptionStatuses.has(data.status)) + consumer.plan === 'free' || + (consumer.status && + stripeValidSubscriptionStatuses.has(consumer.status)) } } ) diff --git a/apps/api/src/db/schema/deployment.ts b/apps/api/src/db/schema/deployment.ts index 1a470368..1300cb41 100644 --- a/apps/api/src/db/schema/deployment.ts +++ b/apps/api/src/db/schema/deployment.ts @@ -105,7 +105,17 @@ export const deploymentsRelations = relations(deployments, ({ one }) => ({ // TODO: virtual authProviders? // TODO: virtual openapi spec? (hide openapi.servers) -// TODO: narrow +export const deploymentSelectSchema = createSelectSchema(deployments, { + // build: z.object({}), + // env: z.object({}), + pricingPlans: z.array(pricingPlanSchema), + coupons: z.array(couponSchema) +}) + .omit({ + _url: true + }) + .openapi('Deployment') + export const deploymentInsertSchema = createInsertSchema(deployments, { id: (schema) => schema.refine((id) => validators.project(id), { @@ -123,18 +133,7 @@ export const deploymentInsertSchema = createInsertSchema(deployments, { // env: z.object({}), pricingPlans: z.array(pricingPlanSchema), coupons: z.array(couponSchema).optional() -}) - -export const deploymentSelectSchema = createSelectSchema(deployments, { - // build: z.object({}), - // env: z.object({}), - pricingPlans: z.array(pricingPlanSchema), - coupons: z.array(couponSchema) -}) - .omit({ - _url: true - }) - .openapi('Deployment') +}).omit({ id: true, createdAt: true, updatedAt: true }) export const deploymentUpdateSchema = createUpdateSchema(deployments).pick({ enabled: true, diff --git a/apps/api/src/db/schema/index.ts b/apps/api/src/db/schema/index.ts index 1af77e5c..01a1e1b5 100644 --- a/apps/api/src/db/schema/index.ts +++ b/apps/api/src/db/schema/index.ts @@ -1,5 +1,6 @@ export * from './consumer' export * from './deployment' +export * from './log-entry' export * from './project' export * from './team' export * from './team-member' diff --git a/apps/api/src/db/schema/log-entry.ts b/apps/api/src/db/schema/log-entry.ts new file mode 100644 index 00000000..42ee9a60 --- /dev/null +++ b/apps/api/src/db/schema/log-entry.ts @@ -0,0 +1,88 @@ +import { relations } from '@fisch0920/drizzle-orm' +import { index, pgTable, text } from '@fisch0920/drizzle-orm/pg-core' + +import { consumers } from './consumer' +import { deployments } from './deployment' +import { projects } from './project' +import { users } from './user' +import { + createInsertSchema, + createSelectSchema, + cuid, + deploymentId, + id, + projectId, + stripeId, + timestamps +} from './utils' + +export const logEntries = pgTable( + 'log_entries', + { + id, + ...timestamps, + + type: text().notNull(), + level: text().notNull().default('info'), // TODO: enum + + // relations + 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() + }, + (table) => [ + index('log_entry_type_idx').on(table.type), + index('log_entry_userId_idx').on(table.userId), + index('log_entry_projectId_idx').on(table.projectId), + index('log_entry_deploymentId_idx').on(table.deploymentId), + index('log_entry_consumerId_idx').on(table.consumerId), + index('log_entry_createdAt_idx').on(table.createdAt), + index('log_entry_updatedAt_idx').on(table.updatedAt) + ] +) + +export const logEntriesRelations = relations(logEntries, ({ one }) => ({ + user: one(users, { + fields: [logEntries.userId], + references: [users.id] + }), + project: one(projects, { + fields: [logEntries.projectId], + references: [projects.id] + }), + deployment: one(deployments, { + fields: [logEntries.deploymentId], + references: [deployments.id] + }), + consumer: one(consumers, { + fields: [logEntries.consumerId], + references: [consumers.id] + }) +})) + +export const logEntrySelectSchema = + createSelectSchema(logEntries).openapi('LogEntry') + +export const logEntryInsertSchema = createInsertSchema(logEntries).omit({ + id: true, + createdAt: true, + updatedAt: true +}) diff --git a/apps/api/src/db/schema/project.ts b/apps/api/src/db/schema/project.ts index eb84d860..0c723307 100644 --- a/apps/api/src/db/schema/project.ts +++ b/apps/api/src/db/schema/project.ts @@ -134,29 +134,6 @@ export const projectsRelations = relations(projects, ({ one, many }) => ({ }) })) -export const projectInsertSchema = createInsertSchema(projects, { - id: (schema) => - schema.refine((id) => validators.project(id), { - message: 'Invalid project id' - }), - - name: (schema) => - schema.refine((name) => validators.projectName(name), { - message: 'Invalid project name' - }) -}) - .pick({ - id: true, - name: true, - userId: true - }) - .refine((data) => { - return { - ...data, - _providerToken: getProviderToken(data) - } - }) - export const projectSelectSchema = createSelectSchema(projects, { stripeMetricProductIds: z.record(z.string(), z.string()).optional() // _webhooks: z.array(webhookSchema), @@ -182,6 +159,29 @@ export const projectSelectSchema = createSelectSchema(projects, { }) .openapi('Project') +export const projectInsertSchema = createInsertSchema(projects, { + id: (schema) => + schema.refine((id) => validators.project(id), { + message: 'Invalid project id' + }), + + name: (schema) => + schema.refine((name) => validators.projectName(name), { + message: 'Invalid project name' + }) +}) + .pick({ + id: true, + name: true, + userId: true + }) + .refine((data) => { + return { + ...data, + _providerToken: getProviderToken(data) + } + }) + // TODO: narrow update schema export const projectUpdateSchema = createUpdateSchema(projects) diff --git a/apps/api/src/db/schema/team-member.ts b/apps/api/src/db/schema/team-member.ts index baced630..56b0d718 100644 --- a/apps/api/src/db/schema/team-member.ts +++ b/apps/api/src/db/schema/team-member.ts @@ -59,14 +59,14 @@ export const teamMembersRelations = relations(teamMembers, ({ one }) => ({ }) })) +export const teamMemberSelectSchema = + createSelectSchema(teamMembers).openapi('TeamMember') + export const teamMemberInsertSchema = createInsertSchema(teamMembers).pick({ userId: true, role: true }) -export const teamMemberSelectSchema = - createSelectSchema(teamMembers).openapi('TeamMember') - export const teamMemberUpdateSchema = createUpdateSchema(teamMembers).pick({ role: true }) diff --git a/apps/api/src/db/schema/team.ts b/apps/api/src/db/schema/team.ts index ca4a6adc..21b24e70 100644 --- a/apps/api/src/db/schema/team.ts +++ b/apps/api/src/db/schema/team.ts @@ -45,6 +45,8 @@ export const teamsRelations = relations(teams, ({ one, many }) => ({ members: many(teamMembers) })) +export const teamSelectSchema = createSelectSchema(teams).openapi('Team') + export const teamInsertSchema = createInsertSchema(teams, { slug: (schema) => schema.refine((slug) => validators.team(slug), { @@ -52,12 +54,7 @@ export const teamInsertSchema = createInsertSchema(teams, { }) }).omit({ id: true, createdAt: true, updatedAt: true, ownerId: true }) -export const teamSelectSchema = createSelectSchema(teams).openapi('Team') - -export const teamUpdateSchema = createUpdateSchema(teams).omit({ - id: true, - createdAt: true, - updatedAt: true, - ownerId: true, - slug: true +export const teamUpdateSchema = createUpdateSchema(teams).pick({ + name: true, + ownerId: true }) diff --git a/apps/api/src/db/schema/user.ts b/apps/api/src/db/schema/user.ts index 3b923201..c91ca980 100644 --- a/apps/api/src/db/schema/user.ts +++ b/apps/api/src/db/schema/user.ts @@ -8,6 +8,7 @@ import { text, uniqueIndex } from '@fisch0920/drizzle-orm/pg-core' +import { hashSync } from 'bcryptjs' import { sha256 } from '@/lib/utils' @@ -68,6 +69,15 @@ export const usersRelations = relations(users, ({ many }) => ({ teamsOwned: many(teams) })) +export const userSelectSchema = createSelectSchema(users).openapi('User') + +function userRefinementHook(user: Partial) { + return { + ...user, + password: user.password ? hashSync(user.password) : undefined + } +} + export const userInsertSchema = createInsertSchema(users, { username: (schema) => schema.refine((username) => validators.username(username), { @@ -77,22 +87,23 @@ export const userInsertSchema = createInsertSchema(users, { email: (schema) => schema.email().optional(), providers: authProvidersSchema.optional() -}).pick({ - username: true, - email: true, - password: true, - firstName: true, - lastName: true, - image: true }) + .pick({ + username: true, + email: true, + password: true, + firstName: true, + lastName: true, + image: true + }) + .refine(userRefinementHook) -export const userSelectSchema = createSelectSchema(users, { - providers: authProvidersSchema -}).openapi('User') - -export const userUpdateSchema = createUpdateSchema(users).pick({ - firstName: true, - lastName: true, - image: true, - isStripeConnectEnabledByDefault: true -}) +export const userUpdateSchema = createUpdateSchema(users) + .pick({ + firstName: true, + lastName: true, + image: true, + password: true, + isStripeConnectEnabledByDefault: true + }) + .refine(userRefinementHook) diff --git a/apps/api/src/db/schema/utils.ts b/apps/api/src/db/schema/utils.ts index af40b21f..a6e924ba 100644 --- a/apps/api/src/db/schema/utils.ts +++ b/apps/api/src/db/schema/utils.ts @@ -50,13 +50,13 @@ export function deploymentId>( export function username>( config?: PgVarcharConfig, never> -): PgVarcharBuilderInitial<'', Writable, 64> { +): PgVarcharBuilderInitial<'', Writable, typeof usernameAndTeamSlugLength> { return varchar({ length: usernameAndTeamSlugLength, ...config }) } export function teamSlug>( config?: PgVarcharConfig, never> -): PgVarcharBuilderInitial<'', Writable, 64> { +): PgVarcharBuilderInitial<'', Writable, typeof usernameAndTeamSlugLength> { return varchar({ length: usernameAndTeamSlugLength, ...config }) } diff --git a/package.json b/package.json index 20e5053b..94291d44 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "type": "git", "url": "git+https://github.com/transitive-bullshit/agentic-platform.git" }, - "packageManager": "pnpm@10.9.0", + "packageManager": "pnpm@10.10.0", "engines": { "node": ">=18" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71b40173..11203653 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -158,6 +158,9 @@ importers: '@workos-inc/node': specifier: ^7.47.0 version: 7.48.0 + bcryptjs: + specifier: ^3.0.2 + version: 3.0.2 eventid: specifier: ^2.0.1 version: 2.0.1 @@ -1504,6 +1507,10 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + bcryptjs@3.0.2: + resolution: {integrity: sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==} + hasBin: true + bignumber.js@9.3.0: resolution: {integrity: sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==} @@ -4899,6 +4906,8 @@ snapshots: base64-js@1.5.1: {} + bcryptjs@3.0.2: {} + bignumber.js@9.3.0: {} brace-expansion@1.1.11: