kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
pull/715/head
rodzic
f259cee1b9
commit
10ee357667
|
@ -34,6 +34,7 @@
|
||||||
"test:unit": "vitest run"
|
"test:unit": "vitest run"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hono/node-server": "^1.14.1",
|
||||||
"@hono/zod-validator": "^0.4.3",
|
"@hono/zod-validator": "^0.4.3",
|
||||||
"@paralleldrive/cuid2": "^2.2.2",
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
"@workos-inc/node": "^7.47.0",
|
"@workos-inc/node": "^7.47.0",
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import type { Context } from 'hono'
|
||||||
|
|
||||||
|
export async function healthCheck(c: Context) {
|
||||||
|
return c.json({ status: 'ok' })
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
|
||||||
|
import type { AuthenticatedEnv } from '@/lib/types'
|
||||||
|
import * as middleware from '@/lib/middleware'
|
||||||
|
|
||||||
|
import { healthCheck } from './health-check'
|
||||||
|
|
||||||
|
export const apiV1 = new Hono()
|
||||||
|
|
||||||
|
const pub = new Hono()
|
||||||
|
const pri = new Hono<AuthenticatedEnv>()
|
||||||
|
|
||||||
|
pub.get('/health', healthCheck)
|
||||||
|
|
||||||
|
apiV1.route('', pub)
|
||||||
|
apiV1.use(middleware.authenticate)
|
||||||
|
apiV1.use(middleware.team)
|
||||||
|
apiV1.use(middleware.me)
|
||||||
|
apiV1.route('', pri)
|
|
@ -7,7 +7,8 @@ export const envSchema = z.object({
|
||||||
.enum(['development', 'test', 'production'])
|
.enum(['development', 'test', 'production'])
|
||||||
.default('development'),
|
.default('development'),
|
||||||
DATABASE_URL: z.string().url(),
|
DATABASE_URL: z.string().url(),
|
||||||
JWT_SECRET: z.string()
|
JWT_SECRET: z.string(),
|
||||||
|
PORT: z.number().default(3000)
|
||||||
})
|
})
|
||||||
|
|
||||||
// eslint-disable-next-line no-process-env
|
// eslint-disable-next-line no-process-env
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
import type { ContentfulStatusCode } from 'hono/utils/http-status'
|
||||||
|
|
||||||
export class HttpError extends Error {
|
export class HttpError extends Error {
|
||||||
readonly statusCode: number
|
readonly statusCode: ContentfulStatusCode
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
statusCode = 500,
|
statusCode = 500,
|
||||||
message
|
message
|
||||||
}: {
|
}: {
|
||||||
statusCode?: number
|
statusCode?: ContentfulStatusCode
|
||||||
message: string
|
message: string
|
||||||
}) {
|
}) {
|
||||||
super(message)
|
super(message)
|
||||||
|
|
|
@ -13,18 +13,19 @@ const jwtMiddleware = jwt({
|
||||||
})
|
})
|
||||||
|
|
||||||
export const authenticate = createMiddleware<AuthenticatedEnv>(
|
export const authenticate = createMiddleware<AuthenticatedEnv>(
|
||||||
async (ctx, next) => {
|
async function authenticateMiddleware(ctx, next) {
|
||||||
console.log(`[${ctx.req.method}] ${ctx.req.url}`)
|
console.log(`[${ctx.req.method}] ${ctx.req.url}`)
|
||||||
|
|
||||||
await jwtMiddleware(ctx, async () => {
|
await jwtMiddleware(ctx, async () => {
|
||||||
const payload = ctx.get('jwtPayload')
|
const payload = ctx.get('jwtPayload')
|
||||||
if (payload.type === 'user') {
|
assert(payload, 401, 'Unauthorized')
|
||||||
|
assert(payload.type === 'user', 401, 'Unauthorized')
|
||||||
|
|
||||||
const user = await db.query.users.findFirst({
|
const user = await db.query.users.findFirst({
|
||||||
where: eq(schema.users.id, payload.userId)
|
where: eq(schema.users.id, payload.userId)
|
||||||
})
|
})
|
||||||
assert(user, 401, 'Unauthorized')
|
assert(user, 401, 'Unauthorized')
|
||||||
ctx.set('user', user)
|
ctx.set('user', user)
|
||||||
}
|
|
||||||
|
|
||||||
await next()
|
await next()
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import type { ContentfulStatusCode } from 'hono/utils/http-status'
|
||||||
|
import { createMiddleware } from 'hono/factory'
|
||||||
|
import { HTTPException } from 'hono/http-exception'
|
||||||
|
|
||||||
|
import type { AuthenticatedEnv } from '@/lib/types'
|
||||||
|
|
||||||
|
import { HttpError } from '../errors'
|
||||||
|
|
||||||
|
export const errorHandler = createMiddleware<AuthenticatedEnv>(
|
||||||
|
async function errorHandlerMiddleware(ctx, next) {
|
||||||
|
try {
|
||||||
|
await next()
|
||||||
|
|
||||||
|
if (!ctx.res.status) {
|
||||||
|
throw new HttpError({ statusCode: 404, message: 'Not Found' })
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
let message = 'Internal Server Error'
|
||||||
|
let status: ContentfulStatusCode = 500
|
||||||
|
|
||||||
|
if (err instanceof HTTPException) {
|
||||||
|
message = err.message
|
||||||
|
status = err.status
|
||||||
|
} else if (err instanceof HttpError) {
|
||||||
|
message = err.message
|
||||||
|
status = err.statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status >= 500) {
|
||||||
|
console.error('http error', status, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.json({ error: message }, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
|
@ -1,3 +1,5 @@
|
||||||
export * from './authenticate'
|
export * from './authenticate'
|
||||||
|
export * from './error-handler'
|
||||||
export * from './me'
|
export * from './me'
|
||||||
|
export * from './response-time'
|
||||||
export * from './team'
|
export * from './team'
|
||||||
|
|
|
@ -2,13 +2,16 @@ import { createMiddleware } from 'hono/factory'
|
||||||
|
|
||||||
import type { AuthenticatedEnv } from '@/lib/types'
|
import type { AuthenticatedEnv } from '@/lib/types'
|
||||||
|
|
||||||
export const me = createMiddleware<AuthenticatedEnv>(async (ctx, next) => {
|
export const me = createMiddleware<AuthenticatedEnv>(
|
||||||
|
async function meMiddleware(ctx, next) {
|
||||||
const user = ctx.get('user')
|
const user = ctx.get('user')
|
||||||
const regex = /^\/(me)(\/|$)/
|
const regex = /^\/(me)(\/|$)/
|
||||||
|
|
||||||
if (user && regex.test(ctx.req.path)) {
|
if (user && regex.test(ctx.req.path)) {
|
||||||
|
// TODO: redirect instead?
|
||||||
ctx.req.path = ctx.req.path.replace(regex, `/users/${user.id}$2`)
|
ctx.req.path = ctx.req.path.replace(regex, `/users/${user.id}$2`)
|
||||||
}
|
}
|
||||||
|
|
||||||
await next()
|
await next()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { createMiddleware } from 'hono/factory'
|
||||||
|
|
||||||
|
import type { AuthenticatedEnv } from '@/lib/types'
|
||||||
|
|
||||||
|
export const responseTime = createMiddleware<AuthenticatedEnv>(
|
||||||
|
async function responseTimeMiddleware(ctx, next) {
|
||||||
|
const start = Date.now()
|
||||||
|
await next()
|
||||||
|
const duration = Date.now() - start
|
||||||
|
ctx.res.headers.set('X-Response-Time', `${duration}ms`)
|
||||||
|
}
|
||||||
|
)
|
|
@ -7,7 +7,8 @@ import { db, schema } from '@/db'
|
||||||
import { aclTeamMember } from '../acl-team-member'
|
import { aclTeamMember } from '../acl-team-member'
|
||||||
import { assert } from '../utils'
|
import { assert } from '../utils'
|
||||||
|
|
||||||
export const team = createMiddleware<AuthenticatedEnv>(async (ctx, next) => {
|
export const team = createMiddleware<AuthenticatedEnv>(
|
||||||
|
async function teamMiddleware(ctx, next) {
|
||||||
const teamId = ctx.req.query('teamId')
|
const teamId = ctx.req.query('teamId')
|
||||||
const user = ctx.get('user')
|
const user = ctx.get('user')
|
||||||
|
|
||||||
|
@ -27,4 +28,5 @@ export const team = createMiddleware<AuthenticatedEnv>(async (ctx, next) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
await next()
|
await next()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
|
import { serve } from '@hono/node-server'
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
|
import { compress } from 'hono/compress'
|
||||||
|
import { cors } from 'hono/cors'
|
||||||
|
|
||||||
|
import { apiV1 } from '@/api-v1'
|
||||||
|
import { env } from '@/lib/env'
|
||||||
import * as middleware from '@/lib/middleware'
|
import * as middleware from '@/lib/middleware'
|
||||||
|
|
||||||
import type { AuthenticatedEnv } from './lib/types'
|
|
||||||
|
|
||||||
export const app = new Hono()
|
export const app = new Hono()
|
||||||
|
|
||||||
const pub = new Hono()
|
app.use(compress())
|
||||||
const pri = new Hono<AuthenticatedEnv>()
|
app.use(middleware.responseTime)
|
||||||
|
app.use(middleware.errorHandler)
|
||||||
|
app.use(cors())
|
||||||
|
|
||||||
app.route('/', pub)
|
app.route('/v1', apiV1)
|
||||||
app.use('*', middleware.authenticate)
|
|
||||||
app.use('*', middleware.team)
|
serve({
|
||||||
app.use('*', middleware.me)
|
fetch: app.fetch,
|
||||||
app.route('/', pri)
|
port: env.PORT
|
||||||
|
})
|
||||||
|
|
|
@ -116,6 +116,9 @@ importers:
|
||||||
|
|
||||||
apps/api:
|
apps/api:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@hono/node-server':
|
||||||
|
specifier: ^1.14.1
|
||||||
|
version: 1.14.1(hono@4.7.7)
|
||||||
'@hono/zod-validator':
|
'@hono/zod-validator':
|
||||||
specifier: ^0.4.3
|
specifier: ^0.4.3
|
||||||
version: 0.4.3(hono@4.7.7)(zod@3.24.3)
|
version: 0.4.3(hono@4.7.7)(zod@3.24.3)
|
||||||
|
@ -507,6 +510,12 @@ packages:
|
||||||
prettier: '>= 3'
|
prettier: '>= 3'
|
||||||
typescript: '>= 5'
|
typescript: '>= 5'
|
||||||
|
|
||||||
|
'@hono/node-server@1.14.1':
|
||||||
|
resolution: {integrity: sha512-vmbuM+HPinjWzPe7FFPWMMQMsbKE9gDPhaH0FFdqbGpkT5lp++tcWDTxwBl5EgS5y6JVgIaCdjeHRfQ4XRBRjQ==}
|
||||||
|
engines: {node: '>=18.14.1'}
|
||||||
|
peerDependencies:
|
||||||
|
hono: ^4
|
||||||
|
|
||||||
'@hono/zod-validator@0.4.3':
|
'@hono/zod-validator@0.4.3':
|
||||||
resolution: {integrity: sha512-xIgMYXDyJ4Hj6ekm9T9Y27s080Nl9NXHcJkOvkXPhubOLj8hZkOL8pDnnXfvCf5xEE8Q4oMFenQUZZREUY2gqQ==}
|
resolution: {integrity: sha512-xIgMYXDyJ4Hj6ekm9T9Y27s080Nl9NXHcJkOvkXPhubOLj8hZkOL8pDnnXfvCf5xEE8Q4oMFenQUZZREUY2gqQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -3151,6 +3160,10 @@ snapshots:
|
||||||
- supports-color
|
- supports-color
|
||||||
- vitest
|
- vitest
|
||||||
|
|
||||||
|
'@hono/node-server@1.14.1(hono@4.7.7)':
|
||||||
|
dependencies:
|
||||||
|
hono: 4.7.7
|
||||||
|
|
||||||
'@hono/zod-validator@0.4.3(hono@4.7.7)(zod@3.24.3)':
|
'@hono/zod-validator@0.4.3(hono@4.7.7)(zod@3.24.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
hono: 4.7.7
|
hono: 4.7.7
|
||||||
|
|
Ładowanie…
Reference in New Issue