pull/715/head
Travis Fischer 2025-04-28 06:46:27 +07:00
rodzic 7dd05666ce
commit 30d909c6f1
6 zmienionych plików z 193 dodań i 124 usunięć

Wyświetl plik

@ -5,6 +5,7 @@ import * as middleware from '@/lib/middleware'
import { registerHealthCheck } from './health-check'
import { registerV1UsersGetUser } from './users/get-user'
import { registerV1UsersUpdateUser } from './users/update-user'
export const apiV1 = new OpenAPIHono()
@ -13,8 +14,9 @@ const pri = new OpenAPIHono<AuthenticatedEnv>()
registerHealthCheck(pub)
// users
// users crud
registerV1UsersGetUser(pri)
registerV1UsersUpdateUser(pri)
apiV1.route('/', pub)
apiV1.use(middleware.authenticate)

Wyświetl plik

@ -9,8 +9,7 @@ const ParamsSchema = z.object({
param: {
name: 'userId',
in: 'path'
},
example: 'pfh0haxfpzowht3oi213cqos'
}
})
})
@ -37,13 +36,8 @@ const route = createRoute({
}
})
export type Route = typeof route
export type V1UsersGetUserResponse = z.infer<
(typeof route.responses)[200]['content']['application/json']['schema']
>
export const registerV1UsersGetUser = (app: OpenAPIHono<AuthenticatedEnv>) =>
app.openapi(route, async (c) => {
export function registerV1UsersGetUser(app: OpenAPIHono<AuthenticatedEnv>) {
return app.openapi(route, async (c) => {
const { userId } = c.req.valid('param')
const user = await db.query.users.findFirst({
@ -53,3 +47,4 @@ export const registerV1UsersGetUser = (app: OpenAPIHono<AuthenticatedEnv>) =>
return c.json(parseZodSchema(schema.userSelectSchema, user))
})
}

Wyświetl plik

@ -0,0 +1,61 @@
import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types'
import { db, eq, schema, userIdSchema } from '@/db'
import { assert, parseZodSchema } from '@/lib/utils'
const ParamsSchema = z.object({
userId: userIdSchema.openapi({
param: {
name: 'userId',
in: 'path'
}
})
})
const route = createRoute({
tags: ['users'],
operationId: 'updateUser',
method: 'put',
path: 'users/{userId}',
security: [{ bearerAuth: [] }],
request: {
params: ParamsSchema,
body: {
required: true,
content: {
'application/json': {
schema: schema.userUpdateSchema
}
}
}
},
responses: {
200: {
description: 'A user object',
content: {
'application/json': {
schema: schema.userSelectSchema
}
}
}
// TODO
// ...openApiErrorResponses
}
})
export function registerV1UsersUpdateUser(app: OpenAPIHono<AuthenticatedEnv>) {
return app.openapi(route, async (c) => {
const { userId } = c.req.valid('param')
const body = c.req.valid('json')
const user = await db
.update(schema.users)
.set(body)
.where(eq(schema.users.id, userId))
.returning()
assert(user, 404, `User not found: ${userId}`)
return c.json(parseZodSchema(schema.userSelectSchema, user))
})
}

Wyświetl plik

@ -0,0 +1,113 @@
// TODO: Currently unused after forking @fisch0920/drizzle-zod.
export function makeNullablePropsOptional<Schema extends z.AnyZodObject>(
schema: Schema
): z.ZodObject<{
[key in keyof Schema['shape']]: Schema['shape'][key] extends z.ZodNullable<
infer T
>
? z.ZodOptional<T>
: Schema['shape'][key]
}> {
const entries = Object.entries(schema.shape)
const newProps: any = {}
for (const [key, value] of entries) {
newProps[key] =
value instanceof z.ZodNullable ? value.unwrap().optional() : value
return newProps
}
return z.object(newProps) as any
}
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/@fisch0920/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

@ -89,4 +89,9 @@ export const userSelectSchema = createSelectSchema(users, {
providers: authProvidersSchema
}).openapi('User')
export const userUpdateSchema = createUpdateSchema(users)
export const userUpdateSchema = createUpdateSchema(users).pick({
firstName: true,
lastName: true,
image: true,
isStripeConnectEnabledByDefault: true
})

Wyświetl plik

@ -13,6 +13,9 @@ import { createSchemaFactory } from '@fisch0920/drizzle-zod'
import { z } from '@hono/zod-openapi'
import { createId } from '@paralleldrive/cuid2'
/**
* `cuid2`
*/
export function cuid<U extends string, T extends Readonly<[U, ...U[]]>>(
config?: PgVarcharConfig<T | Writable<T>, never>
): PgVarcharBuilderInitial<'', Writable<T>, 24> {
@ -43,6 +46,9 @@ export function deploymentId<U extends string, T extends Readonly<[U, ...U[]]>>(
return varchar({ length: 160, ...config })
}
/**
* Default `id` primary key as a cuid2
*/
export const id = varchar('id', { length: 24 })
.primaryKey()
.$defaultFn(createId)
@ -81,116 +87,3 @@ export const { createInsertSchema, createSelectSchema, createUpdateSchema } =
date: true
}
})
// TODO: Currently unused after forking @fisch0920/drizzle-zod.
// export function makeNullablePropsOptional<Schema extends z.AnyZodObject>(
// schema: Schema
// ): z.ZodObject<{
// [key in keyof Schema['shape']]: Schema['shape'][key] extends z.ZodNullable<
// infer T
// >
// ? z.ZodOptional<T>
// : Schema['shape'][key]
// }> {
// const entries = Object.entries(schema.shape)
// const newProps: any = {}
// for (const [key, value] of entries) {
// newProps[key] =
// value instanceof z.ZodNullable ? value.unwrap().optional() : value
// return newProps
// }
// return z.object(newProps) as any
// }
// 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/@fisch0920/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')