diff --git a/apps/api/src/api-v1/consumers/admin-get-consumer-by-token.ts b/apps/api/src/api-v1/consumers/admin-get-consumer-by-token.ts index fb4d9878..57f83de9 100644 --- a/apps/api/src/api-v1/consumers/admin-get-consumer-by-token.ts +++ b/apps/api/src/api-v1/consumers/admin-get-consumer-by-token.ts @@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi' import type { AuthenticatedEnv } from '@/lib/types' import { db, eq, schema } from '@/db' 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' @@ -26,9 +31,9 @@ const route = createRoute({ schema: schema.consumerSelectSchema } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses, + ...openapiErrorResponse404 } }) diff --git a/apps/api/src/api-v1/consumers/get-consumer.ts b/apps/api/src/api-v1/consumers/get-consumer.ts index 76fefa48..d3bdf82b 100644 --- a/apps/api/src/api-v1/consumers/get-consumer.ts +++ b/apps/api/src/api-v1/consumers/get-consumer.ts @@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi' import type { AuthenticatedEnv } from '@/lib/types' import { db, eq, schema } from '@/db' import { acl } from '@/lib/acl' -import { assert, parseZodSchema } from '@/lib/utils' +import { + assert, + openapiErrorResponse404, + openapiErrorResponses, + parseZodSchema +} from '@/lib/utils' import { consumerIdParamsSchema, populateConsumerSchema } from './schemas' @@ -26,9 +31,9 @@ const route = createRoute({ schema: schema.consumerSelectSchema } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses, + ...openapiErrorResponse404 } }) diff --git a/apps/api/src/api-v1/consumers/list-consumers.ts b/apps/api/src/api-v1/consumers/list-consumers.ts index 85a75e05..38b68068 100644 --- a/apps/api/src/api-v1/consumers/list-consumers.ts +++ b/apps/api/src/api-v1/consumers/list-consumers.ts @@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi' import type { AuthenticatedEnv } from '@/lib/types' import { db, eq, schema } from '@/db' 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 { paginationAndPopulateConsumerSchema } from './schemas' @@ -27,9 +32,9 @@ const route = createRoute({ schema: z.array(schema.consumerSelectSchema) } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses, + ...openapiErrorResponse404 } }) diff --git a/apps/api/src/api-v1/consumers/upsert-consumer.ts b/apps/api/src/api-v1/consumers/upsert-consumer.ts index 919f871c..ea1d85af 100644 --- a/apps/api/src/api-v1/consumers/upsert-consumer.ts +++ b/apps/api/src/api-v1/consumers/upsert-consumer.ts @@ -7,7 +7,15 @@ import { upsertStripeConnectCustomer } from '@/lib/billing/upsert-stripe-connect import { upsertStripeCustomer } from '@/lib/billing/upsert-stripe-customer' import { upsertStripePricingPlans } from '@/lib/billing/upsert-stripe-pricing-plans' 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({ description: @@ -35,9 +43,11 @@ const route = createRoute({ schema: schema.consumerSelectSchema } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses, + ...openapiErrorResponse404, + ...openapiErrorResponse409, + ...openapiErrorResponse410 } }) diff --git a/apps/api/src/api-v1/index.ts b/apps/api/src/api-v1/index.ts index e5a2c4fa..b731ae48 100644 --- a/apps/api/src/api-v1/index.ts +++ b/apps/api/src/api-v1/index.ts @@ -1,4 +1,5 @@ import { OpenAPIHono } from '@hono/zod-openapi' +import { fromError } from 'zod-validation-error' import type { AuthenticatedEnv } from '@/lib/types' import * as middleware from '@/lib/middleware' @@ -22,7 +23,18 @@ import { registerV1UsersGetUser } from './users/get-user' import { registerV1UsersUpdateUser } from './users/update-user' 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 const pub = new OpenAPIHono() diff --git a/apps/api/src/api-v1/projects/create-project.ts b/apps/api/src/api-v1/projects/create-project.ts index 08a5bebd..ee4f197f 100644 --- a/apps/api/src/api-v1/projects/create-project.ts +++ b/apps/api/src/api-v1/projects/create-project.ts @@ -5,7 +5,12 @@ import { db, schema } from '@/db' import { aclTeamMember } from '@/lib/acl-team-member' import { getProviderToken } from '@/lib/auth/get-provider-token' 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({ description: 'Creates a new project.', @@ -32,9 +37,8 @@ const route = createRoute({ schema: schema.projectSelectSchema } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses } }) @@ -64,7 +68,7 @@ export function registerV1ProjectsCreateProject( _providerToken: getProviderToken({ id }) }) .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)) }) diff --git a/apps/api/src/api-v1/projects/get-project.ts b/apps/api/src/api-v1/projects/get-project.ts index 0b867fa9..97a5f9a5 100644 --- a/apps/api/src/api-v1/projects/get-project.ts +++ b/apps/api/src/api-v1/projects/get-project.ts @@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi' import type { AuthenticatedEnv } from '@/lib/types' import { db, eq, schema } from '@/db' import { acl } from '@/lib/acl' -import { assert, parseZodSchema } from '@/lib/utils' +import { + assert, + openapiErrorResponse404, + openapiErrorResponses, + parseZodSchema +} from '@/lib/utils' import { populateProjectSchema, projectIdParamsSchema } from './schemas' @@ -26,9 +31,9 @@ const route = createRoute({ schema: schema.projectSelectSchema } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses, + ...openapiErrorResponse404 } }) diff --git a/apps/api/src/api-v1/projects/list-projects.ts b/apps/api/src/api-v1/projects/list-projects.ts index 36db689a..710d8ca4 100644 --- a/apps/api/src/api-v1/projects/list-projects.ts +++ b/apps/api/src/api-v1/projects/list-projects.ts @@ -3,7 +3,7 @@ import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi' import type { AuthenticatedEnv } from '@/lib/types' import { db, eq, schema } from '@/db' import { ensureAuthUser } from '@/lib/ensure-auth-user' -import { parseZodSchema } from '@/lib/utils' +import { openapiErrorResponses, parseZodSchema } from '@/lib/utils' import { paginationAndPopulateProjectSchema } from './schemas' @@ -25,9 +25,8 @@ const route = createRoute({ schema: z.array(schema.projectSelectSchema) } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses } }) diff --git a/apps/api/src/api-v1/projects/update-project.ts b/apps/api/src/api-v1/projects/update-project.ts index df987fc3..59395802 100644 --- a/apps/api/src/api-v1/projects/update-project.ts +++ b/apps/api/src/api-v1/projects/update-project.ts @@ -2,7 +2,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi' import type { AuthenticatedEnv } from '@/lib/types' import { db, eq, schema } from '@/db' -import { assert, parseZodSchema } from '@/lib/utils' +import { + assert, + openapiErrorResponse404, + openapiErrorResponses, + parseZodSchema +} from '@/lib/utils' import { projectIdParamsSchema } from './schemas' @@ -32,9 +37,9 @@ const route = createRoute({ schema: schema.projectSelectSchema } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses, + ...openapiErrorResponse404 } }) @@ -50,7 +55,7 @@ export function registerV1ProjectsUpdateProject( .set(body) .where(eq(schema.projects.id, projectId)) .returning() - assert(project, 404, `Failed to update project "${projectId}"`) + assert(project, 500, `Failed to update project "${projectId}"`) return c.json(parseZodSchema(schema.projectSelectSchema, project)) }) diff --git a/apps/api/src/api-v1/teams/create-team.ts b/apps/api/src/api-v1/teams/create-team.ts index 46aef175..5f2e0438 100644 --- a/apps/api/src/api-v1/teams/create-team.ts +++ b/apps/api/src/api-v1/teams/create-team.ts @@ -4,7 +4,7 @@ import type { AuthenticatedEnv } from '@/lib/types' import { db, schema } from '@/db' import { ensureAuthUser } from '@/lib/ensure-auth-user' import { ensureUniqueTeamSlug } from '@/lib/ensure-unique-team-slug' -import { assert, parseZodSchema } from '@/lib/utils' +import { assert, openapiErrorResponses, parseZodSchema } from '@/lib/utils' const route = createRoute({ description: 'Creates a team.', @@ -31,9 +31,8 @@ const route = createRoute({ schema: schema.teamSelectSchema } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses } }) @@ -52,7 +51,7 @@ export function registerV1TeamsCreateTeam(app: OpenAPIHono) { ownerId: user.id }) .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({ userId: user.id, @@ -63,7 +62,7 @@ export function registerV1TeamsCreateTeam(app: OpenAPIHono) { }) assert( teamMember, - 404, + 500, `Failed to create team member owner for team "${body.slug}"` ) diff --git a/apps/api/src/api-v1/teams/delete-team.ts b/apps/api/src/api-v1/teams/delete-team.ts index dd468f7b..0c943a49 100644 --- a/apps/api/src/api-v1/teams/delete-team.ts +++ b/apps/api/src/api-v1/teams/delete-team.ts @@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi' import type { AuthenticatedEnv } from '@/lib/types' import { db, eq, schema } from '@/db' 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' @@ -25,9 +30,9 @@ const route = createRoute({ schema: schema.teamSelectSchema } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses, + ...openapiErrorResponse404 } }) diff --git a/apps/api/src/api-v1/teams/get-team.ts b/apps/api/src/api-v1/teams/get-team.ts index 76bfddd3..451ccea0 100644 --- a/apps/api/src/api-v1/teams/get-team.ts +++ b/apps/api/src/api-v1/teams/get-team.ts @@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi' import type { AuthenticatedEnv } from '@/lib/types' import { db, eq, schema } from '@/db' 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' @@ -25,9 +30,9 @@ const route = createRoute({ schema: schema.teamSelectSchema } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses, + ...openapiErrorResponse404 } }) diff --git a/apps/api/src/api-v1/teams/list-teams.ts b/apps/api/src/api-v1/teams/list-teams.ts index aab10139..31ea2f5a 100644 --- a/apps/api/src/api-v1/teams/list-teams.ts +++ b/apps/api/src/api-v1/teams/list-teams.ts @@ -2,7 +2,7 @@ import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi' import type { AuthenticatedEnv } from '@/lib/types' import { db, eq, paginationSchema, schema } from '@/db' -import { parseZodSchema } from '@/lib/utils' +import { openapiErrorResponses, parseZodSchema } from '@/lib/utils' const route = createRoute({ description: 'Lists all teams the authenticated user belongs to.', @@ -22,9 +22,8 @@ const route = createRoute({ schema: z.array(schema.teamSelectSchema) } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses } }) diff --git a/apps/api/src/api-v1/teams/members/create-team-member.ts b/apps/api/src/api-v1/teams/members/create-team-member.ts index 6c2f0436..228f74c5 100644 --- a/apps/api/src/api-v1/teams/members/create-team-member.ts +++ b/apps/api/src/api-v1/teams/members/create-team-member.ts @@ -3,7 +3,13 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi' import type { AuthenticatedEnv } from '@/lib/types' import { and, db, eq, schema } from '@/db' 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' @@ -33,9 +39,10 @@ const route = createRoute({ schema: schema.teamMemberSelectSchema } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses, + ...openapiErrorResponse404, + ...openapiErrorResponse409 } }) diff --git a/apps/api/src/api-v1/teams/members/delete-team-member.ts b/apps/api/src/api-v1/teams/members/delete-team-member.ts index de859eb9..48a0cc15 100644 --- a/apps/api/src/api-v1/teams/members/delete-team-member.ts +++ b/apps/api/src/api-v1/teams/members/delete-team-member.ts @@ -4,7 +4,12 @@ import type { AuthenticatedEnv } from '@/lib/types' import { and, db, eq, schema } from '@/db' import { aclTeamAdmin } from '@/lib/acl-team-admin' 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' @@ -26,9 +31,9 @@ const route = createRoute({ schema: schema.teamMemberSelectSchema } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses, + ...openapiErrorResponse404 } }) diff --git a/apps/api/src/api-v1/teams/members/update-team-member.ts b/apps/api/src/api-v1/teams/members/update-team-member.ts index b68140ed..66f347d1 100644 --- a/apps/api/src/api-v1/teams/members/update-team-member.ts +++ b/apps/api/src/api-v1/teams/members/update-team-member.ts @@ -4,7 +4,12 @@ import type { AuthenticatedEnv } from '@/lib/types' import { and, db, eq, schema } from '@/db' import { aclTeamAdmin } from '@/lib/acl-team-admin' 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' @@ -34,9 +39,9 @@ const route = createRoute({ schema: schema.teamMemberSelectSchema } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses, + ...openapiErrorResponse404 } }) diff --git a/apps/api/src/api-v1/teams/update-team.ts b/apps/api/src/api-v1/teams/update-team.ts index 6df9cf29..aebc2f11 100644 --- a/apps/api/src/api-v1/teams/update-team.ts +++ b/apps/api/src/api-v1/teams/update-team.ts @@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi' import type { AuthenticatedEnv } from '@/lib/types' import { db, eq, schema } from '@/db' 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' @@ -33,9 +38,9 @@ const route = createRoute({ schema: schema.teamSelectSchema } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses, + ...openapiErrorResponse404 } }) @@ -50,7 +55,7 @@ export function registerV1TeamsUpdateTeam(app: OpenAPIHono) { .set(body) .where(eq(schema.teams.slug, teamSlug)) .returning() - assert(team, 404, `Failed to update team "${teamSlug}"`) + assert(team, 404, `Team not found "${teamSlug}"`) return c.json(parseZodSchema(schema.teamSelectSchema, team)) }) diff --git a/apps/api/src/api-v1/users/get-user.ts b/apps/api/src/api-v1/users/get-user.ts index da5306bb..76bc4e19 100644 --- a/apps/api/src/api-v1/users/get-user.ts +++ b/apps/api/src/api-v1/users/get-user.ts @@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi' import type { AuthenticatedEnv } from '@/lib/types' import { db, eq, schema } from '@/db' import { acl } from '@/lib/acl' -import { assert, parseZodSchema } from '@/lib/utils' +import { + assert, + openapiErrorResponse404, + openapiErrorResponses, + parseZodSchema +} from '@/lib/utils' import { userIdParamsSchema } from './schemas' @@ -25,9 +30,9 @@ const route = createRoute({ schema: schema.userSelectSchema } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses, + ...openapiErrorResponse404 } }) diff --git a/apps/api/src/api-v1/users/update-user.ts b/apps/api/src/api-v1/users/update-user.ts index 855ae321..b4b10a8f 100644 --- a/apps/api/src/api-v1/users/update-user.ts +++ b/apps/api/src/api-v1/users/update-user.ts @@ -3,7 +3,12 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi' import type { AuthenticatedEnv } from '@/lib/types' import { db, eq, schema } from '@/db' import { acl } from '@/lib/acl' -import { assert, parseZodSchema } from '@/lib/utils' +import { + assert, + openapiErrorResponse404, + openapiErrorResponses, + parseZodSchema +} from '@/lib/utils' import { userIdParamsSchema } from './schemas' @@ -33,9 +38,9 @@ const route = createRoute({ schema: schema.userSelectSchema } } - } - // TODO - // ...openApiErrorResponses + }, + ...openapiErrorResponses, + ...openapiErrorResponse404 } }) diff --git a/apps/api/src/db/schema/temp b/apps/api/src/db/schema/temp index 2f2bfacb..2ad6fb02 100644 --- a/apps/api/src/db/schema/temp +++ b/apps/api/src/db/schema/temp @@ -220,7 +220,7 @@ export function createOpenAPIHonoRoute( } } // TODO - // ...openApiErrorResponses + // ...openppiErrorResponses } }) } diff --git a/apps/api/src/lib/utils.ts b/apps/api/src/lib/utils.ts index 5ab8536d..4ecd9ab5 100644 --- a/apps/api/src/lib/utils.ts +++ b/apps/api/src/lib/utils.ts @@ -2,6 +2,7 @@ import { createHash, randomUUID } from 'node:crypto' import type { ContentfulStatusCode } from 'hono/utils/http-status' import type { ZodSchema } from 'zod' +import { z } from '@hono/zod-openapi' import { HttpError, ZodValidationError } from './errors' @@ -49,3 +50,47 @@ export function parseZodSchema( }) } } + +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