pull/715/head
Travis Fischer 2025-04-26 19:01:05 +07:00
rodzic f2f33948ef
commit bf6fbb0e65
7 zmienionych plików z 214 dodań i 75 usunięć

Wyświetl plik

@ -18,6 +18,7 @@ export {
and, and,
arrayContained, arrayContained,
arrayContains, arrayContains,
arrayOverlaps,
between, between,
eq, eq,
exists, exists,

Wyświetl plik

@ -18,6 +18,8 @@ import {
createUpdateSchema, createUpdateSchema,
cuid, cuid,
deploymentId, deploymentId,
optionalCuid,
optionalText,
projectId, projectId,
timestamps timestamps
} from './utils' } from './utils'
@ -30,18 +32,18 @@ export const deployments = pgTable(
...timestamps, ...timestamps,
hash: text().notNull(), hash: text().notNull(),
version: text(), version: optionalText(),
enabled: boolean().notNull().default(true), enabled: boolean().default(true).notNull(),
published: boolean().notNull().default(false), published: boolean().default(false).notNull(),
description: text().notNull().default(''), description: text().default('').notNull(),
readme: text().notNull().default(''), readme: text().default('').notNull(),
userId: cuid() userId: cuid()
.notNull() .notNull()
.references(() => users.id), .references(() => users.id),
teamId: cuid().references(() => teams.id), teamId: optionalCuid().references(() => teams.id),
projectId: projectId() projectId: projectId()
.notNull() .notNull()
.references(() => projects.id, { .references(() => projects.id, {
@ -51,9 +53,9 @@ export const deployments = pgTable(
// TODO: tools? // TODO: tools?
// services: jsonb().$type<Service[]>().default([]), // services: jsonb().$type<Service[]>().default([]),
// Environment variables & secrets // TODO: Environment variables & secrets
build: jsonb().$type<object>(), // build: jsonb().$type<object>(),
env: jsonb().$type<object>(), // env: jsonb().$type<object>(),
// TODO: metadata config (logo, keywords, etc) // TODO: metadata config (logo, keywords, etc)
// TODO: webhooks // TODO: webhooks
@ -63,7 +65,7 @@ export const deployments = pgTable(
_url: text().notNull(), _url: text().notNull(),
pricingPlans: jsonb().$type<PricingPlan[]>().notNull(), pricingPlans: jsonb().$type<PricingPlan[]>().notNull(),
coupons: jsonb().$type<Coupon[]>().notNull().default([]) coupons: jsonb().$type<Coupon[]>().default([]).notNull()
}, },
(table) => [ (table) => [
index('deployment_userId_idx').on(table.userId), index('deployment_userId_idx').on(table.userId),
@ -113,15 +115,18 @@ export const deploymentInsertSchema = createInsertSchema(deployments, {
_url: (schema) => schema.url(), _url: (schema) => schema.url(),
build: z.object({}), // build: z.object({}),
env: z.object({}), // env: z.object({}),
pricingPlans: z.array(pricingPlanSchema), pricingPlans: z.array(pricingPlanSchema),
coupons: z.array(couponSchema).optional() coupons: z.array(couponSchema).optional()
}) })
export const deploymentSelectSchema = createSelectSchema(deployments, { export const deploymentSelectSchema = createSelectSchema(deployments, {
build: z.object({}), version: z.string().nonempty().optional(),
env: z.object({}), teamId: z.string().cuid2().optional(),
// build: z.object({}),
// env: z.object({}),
pricingPlans: z.array(pricingPlanSchema), pricingPlans: z.array(pricingPlanSchema),
coupons: z.array(couponSchema) coupons: z.array(couponSchema)
}) })

Wyświetl plik

@ -21,9 +21,10 @@ import {
createSelectSchema, createSelectSchema,
createUpdateSchema, createUpdateSchema,
cuid, cuid,
deploymentId, optionalDeploymentId,
optionalStripeId,
optionalText,
projectId, projectId,
stripeId,
timestamps timestamps
} from './utils' } from './utils'
@ -35,7 +36,7 @@ export const projects = pgTable(
...timestamps, ...timestamps,
name: text().notNull(), name: text().notNull(),
alias: text(), alias: optionalText(),
userId: cuid() userId: cuid()
.notNull() .notNull()
@ -43,38 +44,44 @@ export const projects = pgTable(
teamId: cuid().notNull(), teamId: cuid().notNull(),
// Most recently published Deployment if one exists // Most recently published Deployment if one exists
lastPublishedDeploymentId: deploymentId(), lastPublishedDeploymentId: optionalDeploymentId(),
// Most recent Deployment if one exists // Most recent Deployment if one exists
lastDeploymentId: deploymentId(), lastDeploymentId: optionalDeploymentId(),
applicationFeePercent: integer().notNull().default(20), applicationFeePercent: integer().default(20).notNull(),
// TODO: This is going to need to vary from dev to prod // TODO: This is going to need to vary from dev to prod
isStripeConnectEnabled: boolean().notNull().default(false), isStripeConnectEnabled: boolean().default(false).notNull(),
// All deployments share the same underlying proxy secret // All deployments share the same underlying proxy secret
_secret: text(), _secret: optionalText(),
// Auth token used to access the saasify API on behalf of this project // Auth token used to access the saasify API on behalf of this project
_providerToken: text().notNull(), _providerToken: text().notNull(),
// TODO: Full-text search // TODO: Full-text search
_text: text().default(''), _text: text().default('').notNull(),
_webhooks: jsonb().$type<Webhook[]>().default([]), _webhooks: jsonb().$type<Webhook[]>().default([]).notNull(),
// Stripe products corresponding to the stripe plans across deployments // Stripe products corresponding to the stripe plans across deployments
stripeBaseProductId: stripeId(), stripeBaseProductId: optionalStripeId(),
stripeRequestProductId: stripeId(), stripeRequestProductId: optionalStripeId(),
// [metricSlug: string]: string // [metricSlug: string]: string
stripeMetricProductIds: jsonb().$type<Record<string, string>>().default({}), stripeMetricProductIds: jsonb()
.$type<Record<string, string>>()
.default({})
.notNull(),
// Stripe coupons associated with this project, mapping from unique coupon // Stripe coupons associated with this project, mapping from unique coupon
// hash to stripe coupon id. // hash to stripe coupon id.
// `[hash: string]: string` // `[hash: string]: string`
_stripeCouponIds: jsonb().$type<Record<string, string>>().default({}), _stripeCouponIds: jsonb()
.$type<Record<string, string>>()
.default({})
.notNull(),
// Stripe billing plans associated with this project (created lazily), // Stripe billing plans associated with this project (created lazily),
// mapping from unique plan hash to stripe plan ids for base and request // mapping from unique plan hash to stripe plan ids for base and request
@ -82,7 +89,8 @@ export const projects = pgTable(
// `[hash: string]: { basePlanId: string, requestPlanId: string }` // `[hash: string]: { basePlanId: string, requestPlanId: string }`
_stripePlanIds: jsonb() _stripePlanIds: jsonb()
.$type<Record<string, { basePlanId: string; requestPlanId: string }>>() .$type<Record<string, { basePlanId: string; requestPlanId: string }>>()
.default({}), .default({})
.notNull(),
// Connected Stripe account (standard or express). // Connected Stripe account (standard or express).
// If not defined, then subscriptions for this project route through our // If not defined, then subscriptions for this project route through our
@ -91,7 +99,7 @@ export const projects = pgTable(
// the stripeID utility. // the stripeID utility.
// TODO: is it wise to share this between dev and prod? // TODO: is it wise to share this between dev and prod?
// TODO: is it okay for this to be public? // TODO: is it okay for this to be public?
_stripeAccountId: stripeId() _stripeAccountId: optionalStripeId()
}, },
(table) => [ (table) => [
index('project_userId_idx').on(table.userId), index('project_userId_idx').on(table.userId),
@ -151,7 +159,17 @@ export const projectInsertSchema = createInsertSchema(projects, {
}) })
export const projectSelectSchema = createSelectSchema(projects, { export const projectSelectSchema = createSelectSchema(projects, {
stripeMetricProductIds: z.record(z.string(), z.string()).optional() alias: z.string().nonempty().optional(),
lastPublishedDeploymentId: z.string().nonempty().optional(),
lastDeploymentId: z.string().nonempty().optional(),
_secret: z.string().nonempty().optional(),
stripeBaseProductId: z.string().nonempty().optional(),
stripeRequestProductId: z.string().nonempty().optional(),
stripeMetricProductIds: z.record(z.string(), z.string()).optional(),
// _webhooks: z.array(webhookSchema), // _webhooks: z.array(webhookSchema),
// _stripeCouponIds: z.record(z.string(), z.string()).optional(), // _stripeCouponIds: z.record(z.string(), z.string()).optional(),
// _stripePlanIds: z // _stripePlanIds: z
@ -163,6 +181,7 @@ export const projectSelectSchema = createSelectSchema(projects, {
// }) // })
// ) // )
// .optional() // .optional()
_stripeAccountId: z.string().nonempty().optional()
}) })
.omit({ .omit({
_secret: true, _secret: true,

Wyświetl plik

@ -1,17 +1,14 @@
import { z } from '@hono/zod-openapi'
import { relations } from 'drizzle-orm' import { relations } from 'drizzle-orm'
import { import { index, pgTable, primaryKey } from 'drizzle-orm/pg-core'
boolean,
index,
pgTable,
primaryKey,
timestamp
} from 'drizzle-orm/pg-core'
import { teams } from './team' import { teams } from './team'
import { users } from './user' import { users } from './user'
import { import {
createSelectSchema, createSelectSchema,
cuid, cuid,
optionalBoolean,
optionalTimestamp,
teamMemberRoleEnum, teamMemberRoleEnum,
timestamps timestamps
} from './utils' } from './utils'
@ -29,8 +26,8 @@ export const teamMembers = pgTable(
.references(() => teams.id, { onDelete: 'cascade' }), .references(() => teams.id, { onDelete: 'cascade' }),
role: teamMemberRoleEnum().default('user').notNull(), role: teamMemberRoleEnum().default('user').notNull(),
confirmed: boolean().default(false), confirmed: optionalBoolean().default(false).notNull(),
confirmedAt: timestamp({ mode: 'string' }) confirmedAt: optionalTimestamp()
}, },
(table) => [ (table) => [
primaryKey({ columns: [table.userId, table.teamId] }), primaryKey({ columns: [table.userId, table.teamId] }),
@ -52,5 +49,7 @@ export const teamMembersRelations = relations(teamMembers, ({ one }) => ({
}) })
})) }))
export const teamMemberSelectSchema = export const teamMemberSelectSchema = createSelectSchema(teamMembers, {
createSelectSchema(teamMembers).openapi('TeamMember') confirmed: z.boolean(),
confirmedAt: z.string().datetime().optional()
}).openapi('TeamMember')

Wyświetl plik

@ -1,4 +1,5 @@
import { validators } from '@agentic/validators' import { validators } from '@agentic/validators'
import { z } from '@hono/zod-openapi'
import { relations } from 'drizzle-orm' import { relations } from 'drizzle-orm'
import { import {
boolean, boolean,
@ -6,7 +7,6 @@ import {
jsonb, jsonb,
pgTable, pgTable,
text, text,
timestamp,
uniqueIndex uniqueIndex
} from 'drizzle-orm/pg-core' } from 'drizzle-orm/pg-core'
@ -19,7 +19,9 @@ import {
createSelectSchema, createSelectSchema,
createUpdateSchema, createUpdateSchema,
id, id,
stripeId, optionalStripeId,
optionalText,
optionalTimestamp,
timestamps, timestamps,
userRoleEnum userRoleEnum
} from './utils' } from './utils'
@ -33,25 +35,25 @@ export const users = pgTable(
username: text().notNull().unique(), username: text().notNull().unique(),
role: userRoleEnum().default('user').notNull(), role: userRoleEnum().default('user').notNull(),
email: text().unique(), email: optionalText().unique(),
password: text(), password: optionalText(),
// metadata // metadata
firstName: text(), firstName: optionalText(),
lastName: text(), lastName: optionalText(),
image: text(), image: optionalText(),
emailConfirmed: boolean().default(false), emailConfirmed: boolean().default(false).notNull(),
emailConfirmedAt: timestamp({ mode: 'string' }), emailConfirmedAt: optionalTimestamp(),
emailConfirmToken: text().unique().default(sha256()), emailConfirmToken: text().unique().default(sha256()).notNull(),
passwordResetToken: text().unique(), passwordResetToken: optionalText().unique(),
isStripeConnectEnabledByDefault: boolean().default(true), isStripeConnectEnabledByDefault: boolean().default(true).notNull(),
// third-party auth providers // third-party auth providers
providers: jsonb().$type<AuthProviders>().default({}), providers: jsonb().$type<AuthProviders>().default({}).notNull(),
stripeCustomerId: stripeId().unique() stripeCustomerId: optionalStripeId().unique()
}, },
(table) => [ (table) => [
uniqueIndex('user_email_idx').on(table.email), uniqueIndex('user_email_idx').on(table.email),
@ -98,7 +100,19 @@ export const userInsertSchema = createInsertSchema(users, {
}) })
export const userSelectSchema = createSelectSchema(users, { export const userSelectSchema = createSelectSchema(users, {
providers: authProvidersSchema email: z.string().email().optional(),
password: z.string().nonempty().optional(),
firstName: z.string().optional(),
lastName: z.string().optional(),
image: z.string().nonempty().optional(),
emailConfirmedAt: z.string().datetime().optional(),
passwordResetToken: z.string().nonempty().optional(),
providers: authProvidersSchema,
stripeCustomerId: z.string().nonempty().optional()
}).openapi('User') }).openapi('User')
export const userUpdateSchema = createUpdateSchema(users) export const userUpdateSchema = createUpdateSchema(users)

Wyświetl plik

@ -3,6 +3,7 @@ import { z } from '@hono/zod-openapi'
import { createId } from '@paralleldrive/cuid2' import { createId } from '@paralleldrive/cuid2'
import { sql, type Writable } from 'drizzle-orm' import { sql, type Writable } from 'drizzle-orm'
import { import {
customType,
pgEnum, pgEnum,
type PgVarcharBuilderInitial, type PgVarcharBuilderInitial,
type PgVarcharConfig, type PgVarcharConfig,
@ -84,3 +85,94 @@ export const { createInsertSchema, createSelectSchema, createUpdateSchema } =
date: true date: true
} }
}) })
export type ColumnType =
// string
| 'text'
| 'varchar'
| 'timestamp'
| 'stripeId'
| 'projectId'
| 'deploymentId'
| 'cuid'
// boolean
| 'boolean'
// number
| 'integer'
| 'smallint'
| 'bigint'
// json
| 'json'
| 'jsonb'
export type ColumnTypeToTSType<T extends ColumnType> = T extends
| 'text'
| 'varchar'
| 'timestamp'
| 'cuid'
| 'stripeId'
| 'projectId'
| 'deploymentId'
? string
: T extends 'boolean'
? boolean
: T extends 'integer' | 'smallint' | 'bigint'
? number
: never
/**
* @see https://github.com/drizzle-team/drizzle-orm/issues/2745
*/
function optional<
T extends ColumnType,
InferredType extends
| string
| boolean
| number
| object = ColumnTypeToTSType<T>
>(dataType: T) {
return customType<{
data: InferredType | undefined
driverData: InferredType | null
config: T extends 'stripeId'
? {
length: number
}
: never
}>({
dataType() {
if (dataType === 'stripeId') {
return 'varchar({ length: 255 })'
}
if (dataType === 'cuid') {
return 'varchar({ length: 24 })'
}
if (dataType === 'projectId') {
return 'varchar({ length: 130 })'
}
if (dataType === 'deploymentId') {
return 'varchar({ length: 160 })'
}
if (dataType === 'timestamp') {
return 'timestamp({ mode: "string" })'
}
return dataType
},
fromDriver: (v) => v ?? undefined,
toDriver: (v) => v ?? null
})
}
export const optionalText = optional('text')
export const optionalTimestamp = optional('timestamp')
export const optionalBoolean = optional('boolean')
export const optionalVarchar = optional('varchar')
export const optionalCuid = optional('cuid')
export const optionalStripeId = optional('stripeId')
export const optionalProjectId = optional('projectId')
export const optionalDeploymentId = optional('deploymentId')

Wyświetl plik

@ -7,30 +7,39 @@ export type Tables = ExtractTablesWithRelations<typeof schema>
export type User = z.infer<typeof schema.userSelectSchema> export type User = z.infer<typeof schema.userSelectSchema>
// export type User2 = typeof schema.users.$inferSelect
// export type User3 = NullToUndefinedDeep<typeof schema.users.$inferSelect>
export type Team = z.infer<typeof schema.teamSelectSchema> export type Team = z.infer<typeof schema.teamSelectSchema>
export type TeamWithMembers = BuildQueryResult< export type TeamWithMembers = NullToUndefinedDeep<
Tables, BuildQueryResult<Tables, Tables['teams'], { with: { members: true } }>
Tables['teams'],
{ with: { members: true } }
> >
export type TeamMember = z.infer<typeof schema.teamMemberSelectSchema> export type TeamMember = z.infer<typeof schema.teamMemberSelectSchema>
export type TeamMemberWithTeam = BuildQueryResult< export type TeamMemberWithTeam = NullToUndefinedDeep<
Tables, BuildQueryResult<Tables, Tables['teamMembers'], { with: { team: true } }>
Tables['teamMembers'],
{ with: { team: true } }
> >
export type Project = typeof schema.projects.$inferSelect export type Project = z.infer<typeof schema.projectSelectSchema>
export type ProjectWithLastPublishedDeployment = BuildQueryResult< export type ProjectWithLastPublishedDeployment = NullToUndefinedDeep<
Tables, BuildQueryResult<
Tables['projects'], Tables,
{ with: { lastPublishedDeployment: true } } Tables['projects'],
{ with: { lastPublishedDeployment: true } }
>
> >
export type Deployment = typeof schema.deployments.$inferSelect export type Deployment = z.infer<typeof schema.deploymentSelectSchema>
export type DeploymentWithProject = BuildQueryResult< export type DeploymentWithProject = NullToUndefinedDeep<
Tables, BuildQueryResult<Tables, Tables['deployments'], { with: { project: true } }>
Tables['deployments'],
{ with: { project: true } }
> >
export type NullToUndefinedDeep<T> = T extends null
? undefined
: T extends Date
? T
: T extends readonly (infer U)[]
? NullToUndefinedDeep<U>[]
: T extends object
? { [K in keyof T]: NullToUndefinedDeep<T[K]> }
: T