kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: WIP add drizzle postgres db schemas
rodzic
6743740d6f
commit
9efa186ea0
|
@ -0,0 +1,14 @@
|
|||
import 'dotenv/config'
|
||||
|
||||
import { defineConfig } from 'drizzle-kit'
|
||||
|
||||
import { env } from '@/lib/env'
|
||||
|
||||
export default defineConfig({
|
||||
out: './drizzle',
|
||||
schema: './src/db/schema',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
url: env.DATABASE_URL
|
||||
}
|
||||
})
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"name": "agentic-platform-api",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"description": "Agentic platform API.",
|
||||
"author": "Travis Fischer <travis@transitivebullsh.it>",
|
||||
"license": "UNLICENSED",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic-platform.git",
|
||||
"directory": "apps/api"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
"types": "./dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"clean": "del dist",
|
||||
"test": "run-s test:*",
|
||||
"test:lint": "eslint .",
|
||||
"test:typecheck": "tsc --noEmit",
|
||||
"test:unit": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"drizzle-orm": "^0.42.0",
|
||||
"drizzle-zod": "^0.7.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"postgres": "^3.4.5",
|
||||
"type-fest": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"drizzle-kit": "^0.31.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { drizzle, type NodePgClient } from 'drizzle-orm/node-postgres'
|
||||
import postgres from 'postgres'
|
||||
|
||||
import { env } from '@/lib/env'
|
||||
|
||||
import * as schema from './schema'
|
||||
|
||||
let _postgresClient: NodePgClient | undefined
|
||||
const postgresClient =
|
||||
_postgresClient ?? (_postgresClient = postgres(env.DATABASE_URL))
|
||||
|
||||
export const db = drizzle({ client: postgresClient, schema })
|
|
@ -0,0 +1,113 @@
|
|||
import { relations } from 'drizzle-orm'
|
||||
import { boolean, index, jsonb, pgTable, text } from 'drizzle-orm/pg-core'
|
||||
|
||||
import type { Coupon, PricingPlan } from './types'
|
||||
import { projects } from './project'
|
||||
import { teams } from './team'
|
||||
import { users } from './user'
|
||||
import {
|
||||
createInsertSchema,
|
||||
createSelectSchema,
|
||||
createUpdateSchema,
|
||||
timestamps
|
||||
} from './utils'
|
||||
|
||||
export const deployments = pgTable(
|
||||
'deployments',
|
||||
{
|
||||
// namespace/projectName@hash
|
||||
id: text('id').primaryKey(),
|
||||
...timestamps,
|
||||
|
||||
hash: text().notNull(),
|
||||
version: text(),
|
||||
|
||||
enabled: boolean().notNull().default(true),
|
||||
published: boolean().notNull().default(false),
|
||||
|
||||
description: text().notNull().default(''),
|
||||
readme: text().notNull().default(''),
|
||||
|
||||
userId: text()
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
teamId: text().references(() => teams.id),
|
||||
projectId: text()
|
||||
.notNull()
|
||||
.references(() => projects.id, {
|
||||
onDelete: 'cascade'
|
||||
}),
|
||||
|
||||
// TODO: tools?
|
||||
// services: jsonb().$type<Service[]>().default([]),
|
||||
|
||||
// Environment variables & secrets
|
||||
build: jsonb().$type<object>(),
|
||||
env: jsonb().$type<object>(),
|
||||
|
||||
// TODO: metadata config (logo, keywords, etc)
|
||||
// TODO: webhooks
|
||||
// TODO: third-party auth provider config?
|
||||
|
||||
// Backend API URL
|
||||
_url: text().notNull(),
|
||||
|
||||
pricingPlans: jsonb().$type<PricingPlan[]>(),
|
||||
coupons: jsonb().$type<Coupon[]>()
|
||||
},
|
||||
(table) => [
|
||||
index('deployment_userId_idx').on(table.userId),
|
||||
index('deployment_teamId_idx').on(table.teamId),
|
||||
index('deployment_projectId_idx').on(table.projectId),
|
||||
index('deployment_enabled_idx').on(table.enabled),
|
||||
index('deployment_published_idx').on(table.published),
|
||||
index('deployment_version_idx').on(table.version),
|
||||
index('deployment_createdAt_idx').on(table.createdAt),
|
||||
index('deployment_updatedAt_idx').on(table.updatedAt)
|
||||
]
|
||||
)
|
||||
|
||||
export const deploymentsRelations = relations(deployments, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [deployments.userId],
|
||||
references: [users.id]
|
||||
}),
|
||||
team: one(teams, {
|
||||
fields: [deployments.teamId],
|
||||
references: [teams.id]
|
||||
}),
|
||||
project: one(projects, {
|
||||
fields: [deployments.projectId],
|
||||
references: [projects.id]
|
||||
})
|
||||
}))
|
||||
|
||||
// TODO: virtual hasFreeTier
|
||||
// TODO: virtual url
|
||||
// TODO: virtual openApiUrl
|
||||
// TODO: virtual saasUrl
|
||||
// TODO: virtual authProviders?
|
||||
// TODO: virtual openapi spec? (hide openapi.servers)
|
||||
|
||||
export type Deployment = typeof deployments.$inferSelect
|
||||
|
||||
// TODO: narrow
|
||||
export const deploymentInsertSchema = createInsertSchema(deployments, {
|
||||
// TODO: validate deployment id
|
||||
// id: (schema) =>
|
||||
// schema.refine((id) => validators.deployment(id), 'Invalid deployment id')
|
||||
})
|
||||
|
||||
export const deploymentSelectSchema = createSelectSchema(deployments).omit({
|
||||
_url: true
|
||||
})
|
||||
|
||||
// TODO: narrow
|
||||
export const deploymentUpdateSchema = createUpdateSchema(deployments).pick({
|
||||
enabled: true,
|
||||
published: true,
|
||||
version: true,
|
||||
description: true
|
||||
})
|
||||
|
||||
// TODO: add admin select schema which includes all fields?
|
|
@ -0,0 +1,7 @@
|
|||
export * from './deployment'
|
||||
export * from './project'
|
||||
export * from './team'
|
||||
export * from './team-members'
|
||||
export type * from './types'
|
||||
export * from './user'
|
||||
export * from './utils'
|
|
@ -0,0 +1,174 @@
|
|||
import { relations } from 'drizzle-orm'
|
||||
import {
|
||||
boolean,
|
||||
index,
|
||||
integer,
|
||||
jsonb,
|
||||
pgTable,
|
||||
text
|
||||
} from 'drizzle-orm/pg-core'
|
||||
|
||||
import { getProviderToken } from '@/lib/auth/get-provider-token'
|
||||
|
||||
import type { Webhook } from './types'
|
||||
import { deployments } from './deployment'
|
||||
import { teams } from './team'
|
||||
import { users } from './user'
|
||||
import {
|
||||
createInsertSchema,
|
||||
createSelectSchema,
|
||||
createUpdateSchema,
|
||||
timestamps
|
||||
} from './utils'
|
||||
|
||||
export const projects = pgTable(
|
||||
'projects',
|
||||
{
|
||||
// namespace/projectName
|
||||
id: text('id').primaryKey(),
|
||||
...timestamps,
|
||||
|
||||
name: text().notNull(),
|
||||
alias: text(),
|
||||
|
||||
userId: text()
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
teamId: text().notNull(),
|
||||
|
||||
// Most recently published Deployment if one exists
|
||||
lastPublishedDeploymentId: text(),
|
||||
|
||||
// Most recent Deployment if one exists
|
||||
lastDeploymentId: text(),
|
||||
|
||||
applicationFeePercent: integer().notNull().default(20),
|
||||
|
||||
// TODO: This is going to need to vary from dev to prod
|
||||
isStripeConnectEnabled: boolean().notNull().default(false),
|
||||
|
||||
// All deployments share the same underlying proxy secret
|
||||
_secret: text(),
|
||||
|
||||
// Auth token used to access the saasify API on behalf of this project
|
||||
_providerToken: text().notNull(),
|
||||
|
||||
// TODO: Full-text search
|
||||
_text: text().default(''),
|
||||
|
||||
_webhooks: jsonb().$type<Webhook[]>().default([]),
|
||||
|
||||
// Stripe products corresponding to the stripe plans across deployments
|
||||
stripeBaseProduct: text(),
|
||||
stripeRequestProduct: text(),
|
||||
|
||||
// [metricSlug: string]: string
|
||||
stripeMetricProducts: jsonb().$type<Record<string, string>>().default({}),
|
||||
|
||||
// Stripe coupons associated with this project, mapping from unique coupon
|
||||
// hash to stripe coupon id.
|
||||
// `[hash: string]: string`
|
||||
_stripeCoupons: jsonb().$type<Record<string, string>>().default({}),
|
||||
|
||||
// Stripe billing plans associated with this project (created lazily),
|
||||
// mapping from unique plan hash to stripe plan ids for base and request
|
||||
// respectively.
|
||||
// `[hash: string]: { basePlan: string, requestPlan: string }`
|
||||
_stripePlans: jsonb()
|
||||
.$type<Record<string, { basePlan: string; requestPlan: string }>>()
|
||||
.default({}),
|
||||
|
||||
// Connected Stripe account (standard or express).
|
||||
// If not defined, then subscriptions for this project route through our
|
||||
// main Stripe account.
|
||||
// NOTE: the connected account is shared between dev and prod, so we're not using
|
||||
// the stripeID utility.
|
||||
// TODO: is it wise to share this between dev and prod?
|
||||
// TODO: is it okay for this to be public?
|
||||
_stripeAccount: text()
|
||||
},
|
||||
(table) => [
|
||||
index('project_userId_idx').on(table.userId),
|
||||
index('project_teamId_idx').on(table.teamId),
|
||||
index('project_teamId_idx').on(table.teamId),
|
||||
index('project_createdAt_idx').on(table.createdAt),
|
||||
index('project_updatedAt_idx').on(table.updatedAt)
|
||||
]
|
||||
)
|
||||
|
||||
export const projectsRelations = relations(projects, ({ one, many }) => ({
|
||||
user: one(users, {
|
||||
fields: [projects.userId],
|
||||
references: [users.id]
|
||||
}),
|
||||
team: one(teams, {
|
||||
fields: [projects.teamId],
|
||||
references: [teams.id]
|
||||
}),
|
||||
lastPublishedDeployment: one(deployments, {
|
||||
fields: [projects.lastPublishedDeploymentId],
|
||||
references: [deployments.id],
|
||||
relationName: 'lastPublishedDeployment'
|
||||
}),
|
||||
lastDeployment: one(deployments, {
|
||||
fields: [projects.lastDeploymentId],
|
||||
references: [deployments.id],
|
||||
relationName: 'lastDeployment'
|
||||
}),
|
||||
deployments: many(deployments, { relationName: 'deployments' }),
|
||||
publishedDeployments: many(deployments, {
|
||||
relationName: 'publishedDeployments'
|
||||
})
|
||||
}))
|
||||
|
||||
export type Project = typeof projects.$inferSelect
|
||||
|
||||
export const projectInsertSchema = createInsertSchema(projects, {
|
||||
// TODO: validate project id
|
||||
// id: (schema) =>
|
||||
// schema.refine((id) => validators.project(id), 'Invalid project id')
|
||||
// TODO: validate project name
|
||||
// name: (schema) =>
|
||||
// schema.refine((name) => validators.projectName(name), 'Invalid project name')
|
||||
})
|
||||
.pick({
|
||||
id: true,
|
||||
name: true,
|
||||
userId: true
|
||||
})
|
||||
.refine((data) => {
|
||||
return {
|
||||
...data,
|
||||
_providerToken: getProviderToken(data)
|
||||
}
|
||||
})
|
||||
|
||||
export const projectSelectSchema = createSelectSchema(projects).omit({
|
||||
_secret: true,
|
||||
_providerToken: true,
|
||||
_text: true,
|
||||
_webhooks: true,
|
||||
_stripeCoupons: true,
|
||||
_stripePlans: true,
|
||||
_stripeAccount: true
|
||||
})
|
||||
|
||||
// TODO: narrow update schema
|
||||
export const projectUpdateSchema = createUpdateSchema(projects)
|
||||
|
||||
export const projectDebugSelectSchema = createSelectSchema(projects).pick({
|
||||
id: true,
|
||||
name: true,
|
||||
alias: true,
|
||||
userId: true,
|
||||
teamId: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
isStripeConnectEnabled: true,
|
||||
lastPublishedDeploymentId: true,
|
||||
lastDeploymentId: true
|
||||
})
|
||||
|
||||
// TODO: virtual saasUrl
|
||||
// TODO: virtual aliasUrl
|
||||
// TODO: virtual stripeConnectParams
|
|
@ -0,0 +1,47 @@
|
|||
import { relations } from 'drizzle-orm'
|
||||
import {
|
||||
index,
|
||||
pgTable,
|
||||
primaryKey,
|
||||
text,
|
||||
uniqueIndex
|
||||
} from 'drizzle-orm/pg-core'
|
||||
|
||||
import { teams } from './team'
|
||||
import { users } from './user'
|
||||
import { teamMemberRoleEnum, timestamps } from './utils'
|
||||
|
||||
export const teamMembers = pgTable(
|
||||
'team_members',
|
||||
{
|
||||
...timestamps,
|
||||
|
||||
userId: text()
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
teamId: text()
|
||||
.notNull()
|
||||
.references(() => teams.id, { onDelete: 'cascade' }),
|
||||
role: teamMemberRoleEnum().default('user').notNull()
|
||||
},
|
||||
(table) => [
|
||||
primaryKey({ columns: [table.userId, table.teamId] }),
|
||||
uniqueIndex('team_member_user_idx').on(table.userId),
|
||||
uniqueIndex('team_member_team_idx').on(table.teamId),
|
||||
index('team_member_createdAt_idx').on(table.createdAt),
|
||||
index('team_member_updatedAt_idx').on(table.updatedAt)
|
||||
]
|
||||
)
|
||||
|
||||
export const teamMembersRelations = relations(teamMembers, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [teamMembers.userId],
|
||||
references: [users.id]
|
||||
}),
|
||||
team: one(teams, {
|
||||
fields: [teamMembers.teamId],
|
||||
references: [teams.id]
|
||||
})
|
||||
}))
|
||||
|
||||
export type TeamMember = typeof teamMembers.$inferSelect
|
|
@ -0,0 +1,46 @@
|
|||
import { relations } from 'drizzle-orm'
|
||||
import { index, pgTable, text, uniqueIndex } from 'drizzle-orm/pg-core'
|
||||
|
||||
import { teamMembers } from './team-members'
|
||||
import { users } from './user'
|
||||
import {
|
||||
createInsertSchema,
|
||||
createSelectSchema,
|
||||
createUpdateSchema,
|
||||
id,
|
||||
timestamps
|
||||
} from './utils'
|
||||
|
||||
export const teams = pgTable(
|
||||
'teams',
|
||||
{
|
||||
id,
|
||||
...timestamps,
|
||||
|
||||
slug: text().notNull().unique(),
|
||||
name: text().notNull(),
|
||||
|
||||
ownerId: text('owner').notNull()
|
||||
},
|
||||
(table) => [
|
||||
uniqueIndex('team_slug_idx').on(table.slug),
|
||||
index('team_createdAt_idx').on(table.createdAt),
|
||||
index('team_updatedAt_idx').on(table.updatedAt)
|
||||
]
|
||||
)
|
||||
|
||||
export const teamsRelations = relations(teams, ({ one, many }) => ({
|
||||
owner: one(users, {
|
||||
fields: [teams.ownerId],
|
||||
references: [users.id]
|
||||
}),
|
||||
members: many(teamMembers)
|
||||
}))
|
||||
|
||||
export type Team = typeof teams.$inferSelect
|
||||
|
||||
export const teamInsertSchema = createInsertSchema(teams, {
|
||||
slug: (schema) => schema.min(3).max(20) // TODO
|
||||
})
|
||||
export const teamSelectSchema = createSelectSchema(teams)
|
||||
export const teamUpdateSchema = createUpdateSchema(teams).omit({ slug: true })
|
|
@ -0,0 +1,146 @@
|
|||
export type AuthProviderType =
|
||||
| 'github'
|
||||
| 'google'
|
||||
| 'spotify'
|
||||
| 'twitter'
|
||||
| 'linkedin'
|
||||
| 'stripe'
|
||||
|
||||
export type AuthProvider = {
|
||||
provider: AuthProviderType
|
||||
|
||||
/** Provider-specific user id */
|
||||
id: string
|
||||
|
||||
/** Provider-specific username */
|
||||
username?: string
|
||||
|
||||
/** Standard oauth2 access token */
|
||||
accessToken?: string
|
||||
|
||||
/** Standard oauth2 refresh token */
|
||||
refreshToken?: string
|
||||
|
||||
/** Stripe public key */
|
||||
publicKey?: string
|
||||
|
||||
/** OAuth scope(s) */
|
||||
scope?: string
|
||||
}
|
||||
|
||||
export type AuthProviders = {
|
||||
github?: AuthProvider
|
||||
google?: AuthProvider
|
||||
spotify?: AuthProvider
|
||||
twitter?: AuthProvider
|
||||
linkedin?: AuthProvider
|
||||
stripeTest?: AuthProvider
|
||||
stripeLive?: AuthProvider
|
||||
}
|
||||
|
||||
export type Webhook = {
|
||||
url: string
|
||||
events: string[]
|
||||
}
|
||||
|
||||
export type RateLimit = {
|
||||
enabled: boolean
|
||||
|
||||
// informal description that overrides any other properties
|
||||
desc?: string
|
||||
|
||||
interval: number // seconds
|
||||
maxPerInterval: number // unitless
|
||||
}
|
||||
|
||||
export type PricingPlanTier = {
|
||||
unitAmount?: number
|
||||
flatAmount?: number
|
||||
upTo: string
|
||||
} & (
|
||||
| {
|
||||
unitAmount: number
|
||||
}
|
||||
| {
|
||||
flatAmount: number
|
||||
}
|
||||
)
|
||||
|
||||
export type PricingPlanMetric = {
|
||||
// slug acts as a primary key for metrics
|
||||
slug: string
|
||||
|
||||
amount: number
|
||||
|
||||
label: string
|
||||
unitLabel: string
|
||||
|
||||
// TODO: should this default be 'licensed' or 'metered'?
|
||||
// methinks licensed for "sites", "jobs", etc...
|
||||
// TODO: this should probably be explicit since its easy to confuse
|
||||
usageType: 'licensed' | 'metered'
|
||||
|
||||
billingScheme: 'per_unit' | 'tiered'
|
||||
|
||||
tiersMode: 'graduated' | 'volume'
|
||||
tiers: PricingPlanTier[]
|
||||
|
||||
// TODO (low priority): add aggregateUsage
|
||||
|
||||
rateLimit?: RateLimit
|
||||
}
|
||||
|
||||
export type PricingPlan = {
|
||||
name: string
|
||||
slug: string
|
||||
|
||||
desc?: string
|
||||
features: string[]
|
||||
|
||||
auth: boolean
|
||||
amount: number
|
||||
trialPeriodDays?: number
|
||||
|
||||
requests: PricingPlanMetric
|
||||
metrics: PricingPlanMetric[]
|
||||
|
||||
rateLimit?: RateLimit
|
||||
|
||||
// used to uniquely identify this plan across deployments
|
||||
baseId: string
|
||||
|
||||
// used to uniquely identify this plan across deployments
|
||||
requestsId: string
|
||||
|
||||
// [metricSlug: string]: string
|
||||
metricIds: Record<string, string>
|
||||
|
||||
// NOTE: the stripe billing plan id(s) for this PricingPlan are referenced
|
||||
// in the Project._stripePlans mapping via the plan's hash.
|
||||
// NOTE: all metered billing usage is stored in stripe
|
||||
stripeBasePlan: string
|
||||
stripeRequestPlan: string
|
||||
|
||||
// [metricSlug: string]: string
|
||||
stripeMetricPlans: Record<string, string>
|
||||
}
|
||||
|
||||
export type Coupon = {
|
||||
// used to uniquely identify this coupon across deployments
|
||||
id: string
|
||||
|
||||
valid: boolean
|
||||
stripeCoupon: string
|
||||
|
||||
name?: string
|
||||
|
||||
currency?: string
|
||||
amount_off?: number
|
||||
percent_off?: number
|
||||
|
||||
duration: string
|
||||
duration_in_months?: number
|
||||
|
||||
redeem_by?: Date
|
||||
max_redemptions?: number
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
import { relations } from 'drizzle-orm'
|
||||
import {
|
||||
boolean,
|
||||
index,
|
||||
jsonb,
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
uniqueIndex
|
||||
} from 'drizzle-orm/pg-core'
|
||||
|
||||
import { sha256 } from '@/lib/utils'
|
||||
|
||||
import type { AuthProviders } from './types'
|
||||
import { teams } from './team'
|
||||
import {
|
||||
createInsertSchema,
|
||||
createSelectSchema,
|
||||
createUpdateSchema,
|
||||
id,
|
||||
timestamps,
|
||||
userRoleEnum
|
||||
} from './utils'
|
||||
|
||||
export const users = pgTable(
|
||||
'users',
|
||||
{
|
||||
id,
|
||||
...timestamps,
|
||||
|
||||
username: text().notNull().unique(),
|
||||
role: userRoleEnum().default('user').notNull(),
|
||||
|
||||
email: text().unique(),
|
||||
password: text(),
|
||||
|
||||
// metadata
|
||||
firstName: text(),
|
||||
lastName: text(),
|
||||
image: text(),
|
||||
|
||||
emailConfirmed: boolean().default(false),
|
||||
emailConfirmedAt: timestamp(),
|
||||
emailConfirmToken: text().unique().default(sha256()),
|
||||
passwordResetToken: text().unique(),
|
||||
|
||||
isStripeConnectEnabledByDefault: boolean().default(true),
|
||||
|
||||
// third-party auth providers
|
||||
providers: jsonb().$type<AuthProviders>().default({}),
|
||||
|
||||
stripeCustomerId: text()
|
||||
},
|
||||
(table) => [
|
||||
uniqueIndex('user_email_idx').on(table.email),
|
||||
uniqueIndex('user_username_idx').on(table.username),
|
||||
uniqueIndex('user_emailConfirmToken_idx').on(table.emailConfirmToken),
|
||||
uniqueIndex('user_passwordResetToken_idx').on(table.passwordResetToken),
|
||||
index('user_createdAt_idx').on(table.createdAt),
|
||||
index('user_updatedAt_idx').on(table.updatedAt)
|
||||
]
|
||||
)
|
||||
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
teamsOwned: many(teams)
|
||||
|
||||
// TODO: team memberships
|
||||
}))
|
||||
|
||||
export type User = typeof users.$inferSelect
|
||||
|
||||
export const userInsertSchema = createInsertSchema(users).pick({
|
||||
username: true,
|
||||
email: true,
|
||||
password: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
image: true
|
||||
})
|
||||
|
||||
export const userSelectSchema = createSelectSchema(users)
|
||||
export const userUpdateSchema = createUpdateSchema(users)
|
|
@ -0,0 +1,24 @@
|
|||
import { createId } from '@paralleldrive/cuid2'
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { pgEnum, text, timestamp } from 'drizzle-orm/pg-core'
|
||||
import { createSchemaFactory } from 'drizzle-zod'
|
||||
|
||||
export const id = text('id').primaryKey().$defaultFn(createId)
|
||||
|
||||
export const timestamps = {
|
||||
createdAt: timestamp('createdAt').notNull().defaultNow(),
|
||||
updatedAt: timestamp('updatedAt')
|
||||
.notNull()
|
||||
.default(sql`now()`)
|
||||
}
|
||||
|
||||
export const userRoleEnum = pgEnum('UserRole', ['user', 'admin'])
|
||||
export const teamMemberRoleEnum = pgEnum('TeamMemberRole', ['user', 'admin'])
|
||||
|
||||
export const { createInsertSchema, createSelectSchema, createUpdateSchema } =
|
||||
createSchemaFactory({
|
||||
coerce: {
|
||||
// Coerce dates / strings to timetamps
|
||||
date: true
|
||||
}
|
||||
})
|
|
@ -0,0 +1,9 @@
|
|||
import jwt from 'jsonwebtoken'
|
||||
|
||||
import { env } from '@/lib/env'
|
||||
|
||||
export function getProviderToken(project: { id: string }) {
|
||||
// TODO: Possibly in the future store stripe account ID as well and require
|
||||
// provider tokens to refresh after account changes?
|
||||
return jwt.sign({ projectId: project.id }, env.JWT_SECRET)
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import 'dotenv/config'
|
||||
|
||||
import { z } from 'zod'
|
||||
|
||||
export const envSchema = z.object({
|
||||
NODE_ENV: z
|
||||
.enum(['development', 'test', 'production'])
|
||||
.default('development'),
|
||||
DATABASE_URL: z.string().url(),
|
||||
JWT_SECRET: z.string()
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-process-env
|
||||
export const env = envSchema.parse(process.env)
|
||||
|
||||
export const isProd = env.NODE_ENV === 'production'
|
|
@ -0,0 +1 @@
|
|||
export type Operation = 'create' | 'read' | 'update' | 'delete' | 'debug'
|
|
@ -0,0 +1,5 @@
|
|||
import { createHash, randomUUID } from 'node:crypto'
|
||||
|
||||
export function sha256(input: string = randomUUID()) {
|
||||
return createHash('sha256').update(input).digest('hex')
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
import '@fisch0920/config/ts-reset'
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "@fisch0920/config/tsconfig-node",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "drizzle.config.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -1,3 +1,15 @@
|
|||
import { config } from '@fisch0920/config/eslint'
|
||||
import drizzle from 'eslint-plugin-drizzle'
|
||||
|
||||
export default [ ...config ]
|
||||
export default [
|
||||
...config,
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
plugins: {
|
||||
drizzle
|
||||
},
|
||||
rules: {
|
||||
...drizzle.configs.recommended.rules
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "agentic-platform",
|
||||
"private": true,
|
||||
"author": "Travis Fischer <travis@transitivebullsh.it>",
|
||||
"license": "PROPRIETARY",
|
||||
"license": "UNLICENSED",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic-platform.git"
|
||||
|
@ -33,6 +33,7 @@
|
|||
"del-cli": "catalog:",
|
||||
"dotenv": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"eslint-plugin-drizzle": "^0.2.3",
|
||||
"lint-staged": "catalog:",
|
||||
"npm-run-all2": "catalog:",
|
||||
"only-allow": "catalog:",
|
||||
|
|
883
pnpm-lock.yaml
883
pnpm-lock.yaml
Plik diff jest za duży
Load Diff
|
@ -1,19 +1,19 @@
|
|||
packages:
|
||||
- packages/*
|
||||
- services/*
|
||||
- apps/*
|
||||
catalog:
|
||||
'@ai-sdk/openai': ^1.3.6
|
||||
'@fisch0920/config': ^1.0.2
|
||||
'@fisch0920/config': ^1.0.3
|
||||
'@modelcontextprotocol/sdk': ^1.8.0
|
||||
'@types/node': ^22.14.0
|
||||
'@types/node': ^22.14.1
|
||||
ai: ^4.2.11
|
||||
cleye: ^1.3.4
|
||||
del-cli: ^6.0.0
|
||||
dotenv: ^16.4.7
|
||||
eslint: ^9.23.0
|
||||
dotenv: ^16.5.0
|
||||
eslint: ^9.25.0
|
||||
exit-hook: ^4.0.0
|
||||
ky: ^1.8.0
|
||||
lint-staged: ^15.5.0
|
||||
lint-staged: ^15.5.1
|
||||
npm-run-all2: ^7.0.2
|
||||
only-allow: ^1.2.1
|
||||
p-map: ^7.0.3
|
||||
|
@ -24,7 +24,7 @@ catalog:
|
|||
tsup: ^8.4.0
|
||||
tsx: ^4.19.3
|
||||
turbo: ^2.5.0
|
||||
type-fest: ^4.39.1
|
||||
typescript: ^5.8.2
|
||||
type-fest: ^4.40.0
|
||||
typescript: ^5.8.3
|
||||
vitest: ^3.1.1
|
||||
zod: ^3.24.2
|
||||
zod: ^3.24.3
|
||||
|
|
Ładowanie…
Reference in New Issue