kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
pull/715/head
rodzic
80f6655ada
commit
ac2d0c41a7
|
@ -1,14 +1,13 @@
|
|||
/* eslint-disable no-process-env */
|
||||
import 'dotenv/config'
|
||||
|
||||
import { defineConfig } from 'drizzle-kit'
|
||||
|
||||
import { env } from './src/lib/env'
|
||||
|
||||
export default defineConfig({
|
||||
out: './drizzle',
|
||||
schema: './src/db/schema',
|
||||
schema: './src/db/schema/index.ts',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
url: env.DATABASE_URL
|
||||
url: process.env.DATABASE_URL!
|
||||
}
|
||||
})
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"drizzle-kit": "^0.31.0"
|
||||
"drizzle-kit": "^0.31.0",
|
||||
"drizzle-orm": "^0.43.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import type { AuthenticatedEnv } from '@/lib/types'
|
|||
import * as middleware from '@/lib/middleware'
|
||||
|
||||
import { registerHealthCheck } from './health-check'
|
||||
import { registerV1ProjectsGetProject } from './projects/get-project'
|
||||
import { registerV1TeamsCreateTeam } from './teams/create-team'
|
||||
import { registerV1TeamsDeleteTeam } from './teams/delete-team'
|
||||
import { registerV1TeamsGetTeam } from './teams/get-team'
|
||||
|
@ -41,6 +42,9 @@ registerV1TeamsMembersCreateTeamMember(pri)
|
|||
registerV1TeamsMembersUpdateTeamMember(pri)
|
||||
registerV1TeamsMembersDeleteTeamMember(pri)
|
||||
|
||||
// Projects crud
|
||||
registerV1ProjectsGetProject(pri)
|
||||
|
||||
// Setup routes and middleware
|
||||
apiV1.route('/', pub)
|
||||
apiV1.use(middleware.authenticate)
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
|
||||
|
||||
import type { AuthenticatedEnv } from '@/lib/types'
|
||||
import {
|
||||
db,
|
||||
eq,
|
||||
// populateProjectSchema,
|
||||
schema
|
||||
} from '@/db'
|
||||
import { acl } from '@/lib/acl'
|
||||
import { assert, parseZodSchema } from '@/lib/utils'
|
||||
|
||||
import { ProjectIdParamsSchema } from './schemas'
|
||||
|
||||
const route = createRoute({
|
||||
description: 'Gets a project',
|
||||
tags: ['projects'],
|
||||
operationId: 'getProject',
|
||||
method: 'get',
|
||||
path: 'projects/{projectId}',
|
||||
security: [{ bearerAuth: [] }],
|
||||
request: {
|
||||
params: ProjectIdParamsSchema
|
||||
// query: populateProjectSchema
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'A project',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: schema.projectSelectSchema
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
// ...openApiErrorResponses
|
||||
}
|
||||
})
|
||||
|
||||
export function registerV1ProjectsGetProject(
|
||||
app: OpenAPIHono<AuthenticatedEnv>
|
||||
) {
|
||||
return app.openapi(route, async (c) => {
|
||||
const { projectId } = c.req.valid('param')
|
||||
// const { populate = [] } = c.req.valid('query')
|
||||
|
||||
const project = await db.query.projects.findFirst({
|
||||
where: eq(schema.projects.id, projectId)
|
||||
// with: Object.fromEntries(populate.map((field) => [field, true]))
|
||||
})
|
||||
assert(project, 404, `Project not found "${projectId}"`)
|
||||
await acl(c, project, { label: 'Project' })
|
||||
|
||||
return c.json(parseZodSchema(schema.projectSelectSchema, project))
|
||||
})
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import { z } from '@hono/zod-openapi'
|
||||
|
||||
import { projectIdSchema } from '@/db'
|
||||
|
||||
export const ProjectIdParamsSchema = z.object({
|
||||
projectId: projectIdSchema.openapi({
|
||||
param: {
|
||||
description: 'Project ID',
|
||||
name: 'projectId',
|
||||
in: 'path'
|
||||
}
|
||||
})
|
||||
})
|
|
@ -6,3 +6,4 @@ export * from './team'
|
|||
export * from './team-member'
|
||||
export type * from './types'
|
||||
export * from './user'
|
||||
export * from './utils'
|
||||
|
|
|
@ -103,13 +103,12 @@ export const projects = pgTable(
|
|||
(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 }) => ({
|
||||
export const projectsRelations = relations(projects, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [projects.userId],
|
||||
references: [users.id]
|
||||
|
@ -127,13 +126,28 @@ export const projectsRelations = relations(projects, ({ one, many }) => ({
|
|||
fields: [projects.lastDeploymentId],
|
||||
references: [deployments.id],
|
||||
relationName: 'lastDeployment'
|
||||
}),
|
||||
deployments: many(deployments, { relationName: 'deployments' }),
|
||||
publishedDeployments: many(deployments, {
|
||||
relationName: 'publishedDeployments'
|
||||
})
|
||||
// deployments: many(deployments, {
|
||||
// relationName: 'deployments'
|
||||
// }),
|
||||
// publishedDeployments: many(deployments, {
|
||||
// relationName: 'publishedDeployments'
|
||||
// })
|
||||
}))
|
||||
|
||||
export type ProjectRelationFields = keyof ReturnType<
|
||||
(typeof projectsRelations)['config']
|
||||
>
|
||||
|
||||
export const projectRelationsSchema: z.ZodType<ProjectRelationFields> = z.enum([
|
||||
'user',
|
||||
'team',
|
||||
'lastPublishedDeployment',
|
||||
'lastDeployment'
|
||||
// 'deployments',
|
||||
// 'publishedDeployments'
|
||||
])
|
||||
|
||||
export const projectSelectSchema = createSelectSchema(projects, {
|
||||
stripeMetricProductIds: z.record(z.string(), z.string()).optional()
|
||||
// _webhooks: z.array(webhookSchema),
|
||||
|
|
|
@ -45,7 +45,7 @@ export const users = pgTable(
|
|||
|
||||
emailConfirmed: boolean().default(false).notNull(),
|
||||
emailConfirmedAt: timestamp(),
|
||||
emailConfirmToken: text().unique().default(sha256()).notNull(),
|
||||
emailConfirmToken: text().unique().notNull(),
|
||||
passwordResetToken: text().unique(),
|
||||
|
||||
isStripeConnectEnabledByDefault: boolean().default(true).notNull(),
|
||||
|
@ -71,13 +71,6 @@ export const usersRelations = relations(users, ({ many }) => ({
|
|||
|
||||
export const userSelectSchema = createSelectSchema(users).openapi('User')
|
||||
|
||||
function userRefinementHook(user: Partial<typeof users.$inferInsert>) {
|
||||
return {
|
||||
...user,
|
||||
password: user.password ? hashSync(user.password) : undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const userInsertSchema = createInsertSchema(users, {
|
||||
username: (schema) =>
|
||||
schema.refine((username) => validators.username(username), {
|
||||
|
@ -96,7 +89,13 @@ export const userInsertSchema = createInsertSchema(users, {
|
|||
lastName: true,
|
||||
image: true
|
||||
})
|
||||
.refine(userRefinementHook)
|
||||
.refine((user) => {
|
||||
return {
|
||||
...user,
|
||||
emailConfirmToken: sha256(),
|
||||
password: user.password ? hashSync(user.password) : undefined
|
||||
}
|
||||
})
|
||||
|
||||
export const userUpdateSchema = createUpdateSchema(users)
|
||||
.pick({
|
||||
|
@ -106,4 +105,9 @@ export const userUpdateSchema = createUpdateSchema(users)
|
|||
password: true,
|
||||
isStripeConnectEnabledByDefault: true
|
||||
})
|
||||
.refine(userRefinementHook)
|
||||
.refine((user) => {
|
||||
return {
|
||||
...user,
|
||||
password: user.password ? hashSync(user.password) : undefined
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { validators } from '@agentic/validators'
|
||||
import { z } from '@hono/zod-openapi'
|
||||
|
||||
import { projectRelationsSchema } from './schema/project'
|
||||
|
||||
function getCuidSchema(idLabel: string) {
|
||||
return z.string().refine((id) => validators.cuid(id), {
|
||||
message: `Invalid ${idLabel}`
|
||||
|
@ -41,6 +43,10 @@ export const paginationSchema = z.object({
|
|||
sortBy: z.enum(['createdAt', 'updatedAt']).default('createdAt').optional()
|
||||
})
|
||||
|
||||
export const populateProjectSchema = z.object({
|
||||
populate: z.array(projectRelationsSchema).default([]).optional()
|
||||
})
|
||||
|
||||
// import type { PgTable, TableConfig } from '@fisch0920/drizzle-orm/pg-core'
|
||||
// import type { AnyZodObject } from 'zod'
|
||||
//
|
||||
|
|
|
@ -3,8 +3,8 @@ import { assert } from './utils'
|
|||
|
||||
export async function acl<
|
||||
TModel extends Record<string, unknown>,
|
||||
TUserField extends keyof TModel = 'user',
|
||||
TTeamField extends keyof TModel = 'team'
|
||||
TUserField extends keyof TModel = 'userId',
|
||||
TTeamField extends keyof TModel = 'teamId'
|
||||
>(
|
||||
ctx: AuthenticatedContext,
|
||||
model: TModel,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { createMiddleware } from 'hono/factory'
|
||||
import { jwt } from 'hono/jwt'
|
||||
import * as jwt from 'hono/jwt'
|
||||
|
||||
import type { AuthenticatedEnv } from '@/lib/types'
|
||||
import { db, eq, schema } from '@/db'
|
||||
|
@ -7,24 +7,40 @@ import { env } from '@/lib/env'
|
|||
|
||||
import { assert } from '../utils'
|
||||
|
||||
const jwtMiddleware = jwt({
|
||||
secret: env.JWT_SECRET
|
||||
})
|
||||
const token = await jwt.sign({ userId: 'test', type: 'user' }, env.JWT_SECRET)
|
||||
console.log({ token })
|
||||
|
||||
export const authenticate = createMiddleware<AuthenticatedEnv>(
|
||||
async function authenticateMiddleware(ctx, next) {
|
||||
await jwtMiddleware(ctx, async () => {
|
||||
const payload = ctx.get('jwtPayload')
|
||||
assert(payload, 401, 'Unauthorized')
|
||||
assert(payload.type === 'user', 401, 'Unauthorized')
|
||||
const credentials = ctx.req.raw.headers.get('Authorization')
|
||||
assert(credentials, 401, 'Unauthorized')
|
||||
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(schema.users.id, payload.userId)
|
||||
})
|
||||
assert(user, 401, 'Unauthorized')
|
||||
ctx.set('user', user)
|
||||
const parts = credentials.split(/\s+/)
|
||||
assert(
|
||||
parts.length === 1 ||
|
||||
(parts.length === 2 && parts[0]?.toLowerCase() === 'bearer'),
|
||||
401,
|
||||
'Unauthorized'
|
||||
)
|
||||
const token = parts.at(-1)
|
||||
assert(token, 401, 'Unauthorized')
|
||||
|
||||
await next()
|
||||
const payload = await jwt.verify(token, env.JWT_SECRET)
|
||||
console.log({ payload })
|
||||
assert(payload, 401, 'Unauthorized')
|
||||
assert(payload.type === 'user', 401, 'Unauthorized')
|
||||
assert(
|
||||
payload.userId && typeof payload.userId === 'string',
|
||||
401,
|
||||
'Unauthorized'
|
||||
)
|
||||
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(schema.users.id, payload.userId)
|
||||
})
|
||||
assert(user, 401, 'Unauthorized')
|
||||
ctx.set('user', user as any)
|
||||
|
||||
await next()
|
||||
}
|
||||
)
|
||||
|
|
|
@ -201,6 +201,9 @@ importers:
|
|||
drizzle-kit:
|
||||
specifier: ^0.31.0
|
||||
version: 0.31.0
|
||||
drizzle-orm:
|
||||
specifier: ^0.43.1
|
||||
version: 0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(postgres@3.4.5)
|
||||
|
||||
packages/validators:
|
||||
dependencies:
|
||||
|
@ -1730,6 +1733,95 @@ packages:
|
|||
resolution: {integrity: sha512-pcKVT+GbfPA+bUovPIilgVOoq+onNBo/YQBG86sf3/GFHkN6lRJPm1l7dKN0IMAk57RQoIm4GUllRrasLlcaSg==}
|
||||
hasBin: true
|
||||
|
||||
drizzle-orm@0.43.1:
|
||||
resolution: {integrity: sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA==}
|
||||
peerDependencies:
|
||||
'@aws-sdk/client-rds-data': '>=3'
|
||||
'@cloudflare/workers-types': '>=4'
|
||||
'@electric-sql/pglite': '>=0.2.0'
|
||||
'@libsql/client': '>=0.10.0'
|
||||
'@libsql/client-wasm': '>=0.10.0'
|
||||
'@neondatabase/serverless': '>=0.10.0'
|
||||
'@op-engineering/op-sqlite': '>=2'
|
||||
'@opentelemetry/api': ^1.4.1
|
||||
'@planetscale/database': '>=1.13'
|
||||
'@prisma/client': '*'
|
||||
'@tidbcloud/serverless': '*'
|
||||
'@types/better-sqlite3': '*'
|
||||
'@types/pg': '*'
|
||||
'@types/sql.js': '*'
|
||||
'@vercel/postgres': '>=0.8.0'
|
||||
'@xata.io/client': '*'
|
||||
better-sqlite3: '>=7'
|
||||
bun-types: '*'
|
||||
expo-sqlite: '>=14.0.0'
|
||||
gel: '>=2'
|
||||
knex: '*'
|
||||
kysely: '*'
|
||||
mysql2: '>=2'
|
||||
pg: '>=8'
|
||||
postgres: '>=3'
|
||||
prisma: '*'
|
||||
sql.js: '>=1'
|
||||
sqlite3: '>=5'
|
||||
peerDependenciesMeta:
|
||||
'@aws-sdk/client-rds-data':
|
||||
optional: true
|
||||
'@cloudflare/workers-types':
|
||||
optional: true
|
||||
'@electric-sql/pglite':
|
||||
optional: true
|
||||
'@libsql/client':
|
||||
optional: true
|
||||
'@libsql/client-wasm':
|
||||
optional: true
|
||||
'@neondatabase/serverless':
|
||||
optional: true
|
||||
'@op-engineering/op-sqlite':
|
||||
optional: true
|
||||
'@opentelemetry/api':
|
||||
optional: true
|
||||
'@planetscale/database':
|
||||
optional: true
|
||||
'@prisma/client':
|
||||
optional: true
|
||||
'@tidbcloud/serverless':
|
||||
optional: true
|
||||
'@types/better-sqlite3':
|
||||
optional: true
|
||||
'@types/pg':
|
||||
optional: true
|
||||
'@types/sql.js':
|
||||
optional: true
|
||||
'@vercel/postgres':
|
||||
optional: true
|
||||
'@xata.io/client':
|
||||
optional: true
|
||||
better-sqlite3:
|
||||
optional: true
|
||||
bun-types:
|
||||
optional: true
|
||||
expo-sqlite:
|
||||
optional: true
|
||||
gel:
|
||||
optional: true
|
||||
knex:
|
||||
optional: true
|
||||
kysely:
|
||||
optional: true
|
||||
mysql2:
|
||||
optional: true
|
||||
pg:
|
||||
optional: true
|
||||
postgres:
|
||||
optional: true
|
||||
prisma:
|
||||
optional: true
|
||||
sql.js:
|
||||
optional: true
|
||||
sqlite3:
|
||||
optional: true
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -5124,6 +5216,12 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
drizzle-orm@0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(postgres@3.4.5):
|
||||
optionalDependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@types/pg': 8.6.1
|
||||
postgres: 3.4.5
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
|
|
Ładowanie…
Reference in New Issue