pull/715/head
Travis Fischer 2025-05-15 02:03:12 +07:00
rodzic 97e9b9b9c5
commit b28b333b36
21 zmienionych plików z 214 dodań i 79 usunięć

Wyświetl plik

@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv } from '@/lib/types'
import { db, eq, schema } from '@/db' import { db, eq, schema } from '@/db'
import { aclAdmin } from '@/lib/acl-admin' import { aclAdmin } from '@/lib/acl-admin'
import { assert, parseZodSchema } from '@/lib/utils' import {
assert,
openapiErrorResponse404,
openapiErrorResponses,
parseZodSchema
} from '@/lib/utils'
import { consumerTokenParamsSchema, populateConsumerSchema } from './schemas' import { consumerTokenParamsSchema, populateConsumerSchema } from './schemas'
@ -26,9 +31,9 @@ const route = createRoute({
schema: schema.consumerSelectSchema schema: schema.consumerSelectSchema
} }
} }
} },
// TODO ...openapiErrorResponses,
// ...openApiErrorResponses ...openapiErrorResponse404
} }
}) })

Wyświetl plik

@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv } from '@/lib/types'
import { db, eq, schema } from '@/db' import { db, eq, schema } from '@/db'
import { acl } from '@/lib/acl' import { acl } from '@/lib/acl'
import { assert, parseZodSchema } from '@/lib/utils' import {
assert,
openapiErrorResponse404,
openapiErrorResponses,
parseZodSchema
} from '@/lib/utils'
import { consumerIdParamsSchema, populateConsumerSchema } from './schemas' import { consumerIdParamsSchema, populateConsumerSchema } from './schemas'
@ -26,9 +31,9 @@ const route = createRoute({
schema: schema.consumerSelectSchema schema: schema.consumerSelectSchema
} }
} }
} },
// TODO ...openapiErrorResponses,
// ...openApiErrorResponses ...openapiErrorResponse404
} }
}) })

Wyświetl plik

@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv } from '@/lib/types'
import { db, eq, schema } from '@/db' import { db, eq, schema } from '@/db'
import { acl } from '@/lib/acl' import { acl } from '@/lib/acl'
import { assert, parseZodSchema } from '@/lib/utils' import {
assert,
openapiErrorResponse404,
openapiErrorResponses,
parseZodSchema
} from '@/lib/utils'
import { projectIdParamsSchema } from '../projects/schemas' import { projectIdParamsSchema } from '../projects/schemas'
import { paginationAndPopulateConsumerSchema } from './schemas' import { paginationAndPopulateConsumerSchema } from './schemas'
@ -27,9 +32,9 @@ const route = createRoute({
schema: z.array(schema.consumerSelectSchema) schema: z.array(schema.consumerSelectSchema)
} }
} }
} },
// TODO ...openapiErrorResponses,
// ...openApiErrorResponses ...openapiErrorResponse404
} }
}) })

Wyświetl plik

@ -7,7 +7,15 @@ import { upsertStripeConnectCustomer } from '@/lib/billing/upsert-stripe-connect
import { upsertStripeCustomer } from '@/lib/billing/upsert-stripe-customer' import { upsertStripeCustomer } from '@/lib/billing/upsert-stripe-customer'
import { upsertStripePricingPlans } from '@/lib/billing/upsert-stripe-pricing-plans' import { upsertStripePricingPlans } from '@/lib/billing/upsert-stripe-pricing-plans'
import { upsertStripeSubscription } from '@/lib/billing/upsert-stripe-subscription' import { upsertStripeSubscription } from '@/lib/billing/upsert-stripe-subscription'
import { assert, parseZodSchema, sha256 } from '@/lib/utils' import {
assert,
openapiErrorResponse404,
openapiErrorResponse409,
openapiErrorResponse410,
openapiErrorResponses,
parseZodSchema,
sha256
} from '@/lib/utils'
const route = createRoute({ const route = createRoute({
description: description:
@ -35,9 +43,11 @@ const route = createRoute({
schema: schema.consumerSelectSchema schema: schema.consumerSelectSchema
} }
} }
} },
// TODO ...openapiErrorResponses,
// ...openApiErrorResponses ...openapiErrorResponse404,
...openapiErrorResponse409,
...openapiErrorResponse410
} }
}) })

Wyświetl plik

@ -1,4 +1,5 @@
import { OpenAPIHono } from '@hono/zod-openapi' import { OpenAPIHono } from '@hono/zod-openapi'
import { fromError } from 'zod-validation-error'
import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv } from '@/lib/types'
import * as middleware from '@/lib/middleware' import * as middleware from '@/lib/middleware'
@ -22,7 +23,18 @@ import { registerV1UsersGetUser } from './users/get-user'
import { registerV1UsersUpdateUser } from './users/update-user' import { registerV1UsersUpdateUser } from './users/update-user'
import { registerV1StripeWebhook } from './webhooks/stripe-webhook' import { registerV1StripeWebhook } from './webhooks/stripe-webhook'
export const apiV1 = new OpenAPIHono() export const apiV1 = new OpenAPIHono({
defaultHook: (result, ctx) => {
if (!result.success) {
return ctx.json(
{
error: fromError(result.error).toString()
},
400
)
}
}
})
// Public routes // Public routes
const pub = new OpenAPIHono() const pub = new OpenAPIHono()

Wyświetl plik

@ -5,7 +5,12 @@ import { db, schema } from '@/db'
import { aclTeamMember } from '@/lib/acl-team-member' import { aclTeamMember } from '@/lib/acl-team-member'
import { getProviderToken } from '@/lib/auth/get-provider-token' import { getProviderToken } from '@/lib/auth/get-provider-token'
import { ensureAuthUser } from '@/lib/ensure-auth-user' import { ensureAuthUser } from '@/lib/ensure-auth-user'
import { assert, parseZodSchema, sha256 } from '@/lib/utils' import {
assert,
openapiErrorResponses,
parseZodSchema,
sha256
} from '@/lib/utils'
const route = createRoute({ const route = createRoute({
description: 'Creates a new project.', description: 'Creates a new project.',
@ -32,9 +37,8 @@ const route = createRoute({
schema: schema.projectSelectSchema schema: schema.projectSelectSchema
} }
} }
} },
// TODO ...openapiErrorResponses
// ...openApiErrorResponses
} }
}) })
@ -64,7 +68,7 @@ export function registerV1ProjectsCreateProject(
_providerToken: getProviderToken({ id }) _providerToken: getProviderToken({ id })
}) })
.returning() .returning()
assert(project, 404, `Failed to create project "${body.name}"`) assert(project, 500, `Failed to create project "${body.name}"`)
return c.json(parseZodSchema(schema.projectSelectSchema, project)) return c.json(parseZodSchema(schema.projectSelectSchema, project))
}) })

Wyświetl plik

@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv } from '@/lib/types'
import { db, eq, schema } from '@/db' import { db, eq, schema } from '@/db'
import { acl } from '@/lib/acl' import { acl } from '@/lib/acl'
import { assert, parseZodSchema } from '@/lib/utils' import {
assert,
openapiErrorResponse404,
openapiErrorResponses,
parseZodSchema
} from '@/lib/utils'
import { populateProjectSchema, projectIdParamsSchema } from './schemas' import { populateProjectSchema, projectIdParamsSchema } from './schemas'
@ -26,9 +31,9 @@ const route = createRoute({
schema: schema.projectSelectSchema schema: schema.projectSelectSchema
} }
} }
} },
// TODO ...openapiErrorResponses,
// ...openApiErrorResponses ...openapiErrorResponse404
} }
}) })

Wyświetl plik

@ -3,7 +3,7 @@ import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv } from '@/lib/types'
import { db, eq, schema } from '@/db' import { db, eq, schema } from '@/db'
import { ensureAuthUser } from '@/lib/ensure-auth-user' import { ensureAuthUser } from '@/lib/ensure-auth-user'
import { parseZodSchema } from '@/lib/utils' import { openapiErrorResponses, parseZodSchema } from '@/lib/utils'
import { paginationAndPopulateProjectSchema } from './schemas' import { paginationAndPopulateProjectSchema } from './schemas'
@ -25,9 +25,8 @@ const route = createRoute({
schema: z.array(schema.projectSelectSchema) schema: z.array(schema.projectSelectSchema)
} }
} }
} },
// TODO ...openapiErrorResponses
// ...openApiErrorResponses
} }
}) })

Wyświetl plik

@ -2,7 +2,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv } from '@/lib/types'
import { db, eq, schema } from '@/db' import { db, eq, schema } from '@/db'
import { assert, parseZodSchema } from '@/lib/utils' import {
assert,
openapiErrorResponse404,
openapiErrorResponses,
parseZodSchema
} from '@/lib/utils'
import { projectIdParamsSchema } from './schemas' import { projectIdParamsSchema } from './schemas'
@ -32,9 +37,9 @@ const route = createRoute({
schema: schema.projectSelectSchema schema: schema.projectSelectSchema
} }
} }
} },
// TODO ...openapiErrorResponses,
// ...openApiErrorResponses ...openapiErrorResponse404
} }
}) })
@ -50,7 +55,7 @@ export function registerV1ProjectsUpdateProject(
.set(body) .set(body)
.where(eq(schema.projects.id, projectId)) .where(eq(schema.projects.id, projectId))
.returning() .returning()
assert(project, 404, `Failed to update project "${projectId}"`) assert(project, 500, `Failed to update project "${projectId}"`)
return c.json(parseZodSchema(schema.projectSelectSchema, project)) return c.json(parseZodSchema(schema.projectSelectSchema, project))
}) })

Wyświetl plik

@ -4,7 +4,7 @@ import type { AuthenticatedEnv } from '@/lib/types'
import { db, schema } from '@/db' import { db, schema } from '@/db'
import { ensureAuthUser } from '@/lib/ensure-auth-user' import { ensureAuthUser } from '@/lib/ensure-auth-user'
import { ensureUniqueTeamSlug } from '@/lib/ensure-unique-team-slug' import { ensureUniqueTeamSlug } from '@/lib/ensure-unique-team-slug'
import { assert, parseZodSchema } from '@/lib/utils' import { assert, openapiErrorResponses, parseZodSchema } from '@/lib/utils'
const route = createRoute({ const route = createRoute({
description: 'Creates a team.', description: 'Creates a team.',
@ -31,9 +31,8 @@ const route = createRoute({
schema: schema.teamSelectSchema schema: schema.teamSelectSchema
} }
} }
} },
// TODO ...openapiErrorResponses
// ...openApiErrorResponses
} }
}) })
@ -52,7 +51,7 @@ export function registerV1TeamsCreateTeam(app: OpenAPIHono<AuthenticatedEnv>) {
ownerId: user.id ownerId: user.id
}) })
.returning() .returning()
assert(team, 404, `Failed to create team "${body.slug}"`) assert(team, 500, `Failed to create team "${body.slug}"`)
const [teamMember] = await tx.insert(schema.teamMembers).values({ const [teamMember] = await tx.insert(schema.teamMembers).values({
userId: user.id, userId: user.id,
@ -63,7 +62,7 @@ export function registerV1TeamsCreateTeam(app: OpenAPIHono<AuthenticatedEnv>) {
}) })
assert( assert(
teamMember, teamMember,
404, 500,
`Failed to create team member owner for team "${body.slug}"` `Failed to create team member owner for team "${body.slug}"`
) )

Wyświetl plik

@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv } from '@/lib/types'
import { db, eq, schema } from '@/db' import { db, eq, schema } from '@/db'
import { aclTeamAdmin } from '@/lib/acl-team-admin' import { aclTeamAdmin } from '@/lib/acl-team-admin'
import { assert, parseZodSchema } from '@/lib/utils' import {
assert,
openapiErrorResponse404,
openapiErrorResponses,
parseZodSchema
} from '@/lib/utils'
import { teamSlugParamsSchema } from './schemas' import { teamSlugParamsSchema } from './schemas'
@ -25,9 +30,9 @@ const route = createRoute({
schema: schema.teamSelectSchema schema: schema.teamSelectSchema
} }
} }
} },
// TODO ...openapiErrorResponses,
// ...openApiErrorResponses ...openapiErrorResponse404
} }
}) })

Wyświetl plik

@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv } from '@/lib/types'
import { db, eq, schema } from '@/db' import { db, eq, schema } from '@/db'
import { aclTeamMember } from '@/lib/acl-team-member' import { aclTeamMember } from '@/lib/acl-team-member'
import { assert, parseZodSchema } from '@/lib/utils' import {
assert,
openapiErrorResponse404,
openapiErrorResponses,
parseZodSchema
} from '@/lib/utils'
import { teamSlugParamsSchema } from './schemas' import { teamSlugParamsSchema } from './schemas'
@ -25,9 +30,9 @@ const route = createRoute({
schema: schema.teamSelectSchema schema: schema.teamSelectSchema
} }
} }
} },
// TODO ...openapiErrorResponses,
// ...openApiErrorResponses ...openapiErrorResponse404
} }
}) })

Wyświetl plik

@ -2,7 +2,7 @@ import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv } from '@/lib/types'
import { db, eq, paginationSchema, schema } from '@/db' import { db, eq, paginationSchema, schema } from '@/db'
import { parseZodSchema } from '@/lib/utils' import { openapiErrorResponses, parseZodSchema } from '@/lib/utils'
const route = createRoute({ const route = createRoute({
description: 'Lists all teams the authenticated user belongs to.', description: 'Lists all teams the authenticated user belongs to.',
@ -22,9 +22,8 @@ const route = createRoute({
schema: z.array(schema.teamSelectSchema) schema: z.array(schema.teamSelectSchema)
} }
} }
} },
// TODO ...openapiErrorResponses
// ...openApiErrorResponses
} }
}) })

Wyświetl plik

@ -3,7 +3,13 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv } from '@/lib/types'
import { and, db, eq, schema } from '@/db' import { and, db, eq, schema } from '@/db'
import { aclTeamAdmin } from '@/lib/acl-team-admin' import { aclTeamAdmin } from '@/lib/acl-team-admin'
import { assert, parseZodSchema } from '@/lib/utils' import {
assert,
openapiErrorResponse404,
openapiErrorResponse409,
openapiErrorResponses,
parseZodSchema
} from '@/lib/utils'
import { teamSlugParamsSchema } from '../schemas' import { teamSlugParamsSchema } from '../schemas'
@ -33,9 +39,10 @@ const route = createRoute({
schema: schema.teamMemberSelectSchema schema: schema.teamMemberSelectSchema
} }
} }
} },
// TODO ...openapiErrorResponses,
// ...openApiErrorResponses ...openapiErrorResponse404,
...openapiErrorResponse409
} }
}) })

Wyświetl plik

@ -4,7 +4,12 @@ import type { AuthenticatedEnv } from '@/lib/types'
import { and, db, eq, schema } from '@/db' import { and, db, eq, schema } from '@/db'
import { aclTeamAdmin } from '@/lib/acl-team-admin' import { aclTeamAdmin } from '@/lib/acl-team-admin'
import { aclTeamMember } from '@/lib/acl-team-member' import { aclTeamMember } from '@/lib/acl-team-member'
import { assert, parseZodSchema } from '@/lib/utils' import {
assert,
openapiErrorResponse404,
openapiErrorResponses,
parseZodSchema
} from '@/lib/utils'
import { teamSlugTeamMemberUserIdParamsSchema } from './schemas' import { teamSlugTeamMemberUserIdParamsSchema } from './schemas'
@ -26,9 +31,9 @@ const route = createRoute({
schema: schema.teamMemberSelectSchema schema: schema.teamMemberSelectSchema
} }
} }
} },
// TODO ...openapiErrorResponses,
// ...openApiErrorResponses ...openapiErrorResponse404
} }
}) })

Wyświetl plik

@ -4,7 +4,12 @@ import type { AuthenticatedEnv } from '@/lib/types'
import { and, db, eq, schema } from '@/db' import { and, db, eq, schema } from '@/db'
import { aclTeamAdmin } from '@/lib/acl-team-admin' import { aclTeamAdmin } from '@/lib/acl-team-admin'
import { aclTeamMember } from '@/lib/acl-team-member' import { aclTeamMember } from '@/lib/acl-team-member'
import { assert, parseZodSchema } from '@/lib/utils' import {
assert,
openapiErrorResponse404,
openapiErrorResponses,
parseZodSchema
} from '@/lib/utils'
import { teamSlugTeamMemberUserIdParamsSchema } from './schemas' import { teamSlugTeamMemberUserIdParamsSchema } from './schemas'
@ -34,9 +39,9 @@ const route = createRoute({
schema: schema.teamMemberSelectSchema schema: schema.teamMemberSelectSchema
} }
} }
} },
// TODO ...openapiErrorResponses,
// ...openApiErrorResponses ...openapiErrorResponse404
} }
}) })

Wyświetl plik

@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv } from '@/lib/types'
import { db, eq, schema } from '@/db' import { db, eq, schema } from '@/db'
import { aclTeamAdmin } from '@/lib/acl-team-admin' import { aclTeamAdmin } from '@/lib/acl-team-admin'
import { assert, parseZodSchema } from '@/lib/utils' import {
assert,
openapiErrorResponse404,
openapiErrorResponses,
parseZodSchema
} from '@/lib/utils'
import { teamSlugParamsSchema } from './schemas' import { teamSlugParamsSchema } from './schemas'
@ -33,9 +38,9 @@ const route = createRoute({
schema: schema.teamSelectSchema schema: schema.teamSelectSchema
} }
} }
} },
// TODO ...openapiErrorResponses,
// ...openApiErrorResponses ...openapiErrorResponse404
} }
}) })
@ -50,7 +55,7 @@ export function registerV1TeamsUpdateTeam(app: OpenAPIHono<AuthenticatedEnv>) {
.set(body) .set(body)
.where(eq(schema.teams.slug, teamSlug)) .where(eq(schema.teams.slug, teamSlug))
.returning() .returning()
assert(team, 404, `Failed to update team "${teamSlug}"`) assert(team, 404, `Team not found "${teamSlug}"`)
return c.json(parseZodSchema(schema.teamSelectSchema, team)) return c.json(parseZodSchema(schema.teamSelectSchema, team))
}) })

Wyświetl plik

@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv } from '@/lib/types'
import { db, eq, schema } from '@/db' import { db, eq, schema } from '@/db'
import { acl } from '@/lib/acl' import { acl } from '@/lib/acl'
import { assert, parseZodSchema } from '@/lib/utils' import {
assert,
openapiErrorResponse404,
openapiErrorResponses,
parseZodSchema
} from '@/lib/utils'
import { userIdParamsSchema } from './schemas' import { userIdParamsSchema } from './schemas'
@ -25,9 +30,9 @@ const route = createRoute({
schema: schema.userSelectSchema schema: schema.userSelectSchema
} }
} }
} },
// TODO ...openapiErrorResponses,
// ...openApiErrorResponses ...openapiErrorResponse404
} }
}) })

Wyświetl plik

@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv } from '@/lib/types'
import { db, eq, schema } from '@/db' import { db, eq, schema } from '@/db'
import { acl } from '@/lib/acl' import { acl } from '@/lib/acl'
import { assert, parseZodSchema } from '@/lib/utils' import {
assert,
openapiErrorResponse404,
openapiErrorResponses,
parseZodSchema
} from '@/lib/utils'
import { userIdParamsSchema } from './schemas' import { userIdParamsSchema } from './schemas'
@ -33,9 +38,9 @@ const route = createRoute({
schema: schema.userSelectSchema schema: schema.userSelectSchema
} }
} }
} },
// TODO ...openapiErrorResponses,
// ...openApiErrorResponses ...openapiErrorResponse404
} }
}) })

Wyświetl plik

@ -220,7 +220,7 @@ export function createOpenAPIHonoRoute<TAuthenticated extends boolean>(
} }
} }
// TODO // TODO
// ...openApiErrorResponses // ...openppiErrorResponses
} }
}) })
} }

Wyświetl plik

@ -2,6 +2,7 @@ import { createHash, randomUUID } from 'node:crypto'
import type { ContentfulStatusCode } from 'hono/utils/http-status' import type { ContentfulStatusCode } from 'hono/utils/http-status'
import type { ZodSchema } from 'zod' import type { ZodSchema } from 'zod'
import { z } from '@hono/zod-openapi'
import { HttpError, ZodValidationError } from './errors' import { HttpError, ZodValidationError } from './errors'
@ -49,3 +50,47 @@ export function parseZodSchema<T>(
}) })
} }
} }
const errorContent = {
'application/json': {
schema: z.object({
error: z.string()
})
}
} as const
export const openapiErrorResponses = {
400: {
description: 'Bad Request',
content: errorContent
},
401: {
description: 'Unauthorized',
content: errorContent
},
403: {
description: 'Forbidden',
content: errorContent
}
} as const
export const openapiErrorResponse404 = {
404: {
description: 'Not Found',
content: errorContent
}
} as const
export const openapiErrorResponse409 = {
409: {
description: 'Conflict',
content: errorContent
}
} as const
export const openapiErrorResponse410 = {
410: {
description: 'Gone',
content: errorContent
}
} as const