diff --git a/api/api/v1/accounts/:id/block.ts b/api/api/v1/accounts/:id/block.ts index 05f39dc8..6634b02a 100644 --- a/api/api/v1/accounts/:id/block.ts +++ b/api/api/v1/accounts/:id/block.ts @@ -1,10 +1,4 @@ -import { - accountNotFound, - apiRoute, - auth, - reusedResponses, - withUserParam, -} from "@/api"; +import { apiRoute, auth, withUserParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -12,6 +6,7 @@ import { } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Relationship } from "@versia/kit/db"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "post", @@ -44,8 +39,9 @@ const route = createRoute({ }, }, }, - 404: accountNotFound, - ...reusedResponses, + 404: ApiError.accountNotFound().schema, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, request: { params: z.object({ diff --git a/api/api/v1/accounts/:id/follow.ts b/api/api/v1/accounts/:id/follow.ts index 86f0857d..084bc174 100644 --- a/api/api/v1/accounts/:id/follow.ts +++ b/api/api/v1/accounts/:id/follow.ts @@ -1,10 +1,4 @@ -import { - accountNotFound, - apiRoute, - auth, - reusedResponses, - withUserParam, -} from "@/api"; +import { apiRoute, auth, withUserParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -13,7 +7,7 @@ import { } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Relationship } from "@versia/kit/db"; -import { ErrorSchema } from "~/types/api"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "post", @@ -51,12 +45,13 @@ const route = createRoute({ "Trying to follow someone that you block or that blocks you", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, - 404: accountNotFound, - ...reusedResponses, + 404: ApiError.accountNotFound().schema, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, request: { params: z.object({ diff --git a/api/api/v1/accounts/:id/followers.ts b/api/api/v1/accounts/:id/followers.ts index 06e5161f..b1ea3677 100644 --- a/api/api/v1/accounts/:id/followers.ts +++ b/api/api/v1/accounts/:id/followers.ts @@ -1,16 +1,11 @@ -import { - accountNotFound, - apiRoute, - auth, - reusedResponses, - withUserParam, -} from "@/api"; +import { apiRoute, auth, withUserParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Timeline } from "@versia/kit/db"; import { Users } from "@versia/kit/tables"; import { and, gt, gte, lt, sql } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -80,8 +75,8 @@ const route = createRoute({ }), }), }, - 404: accountNotFound, - 422: reusedResponses[422], + 404: ApiError.accountNotFound().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/accounts/:id/following.ts b/api/api/v1/accounts/:id/following.ts index 0fc26600..36739c99 100644 --- a/api/api/v1/accounts/:id/following.ts +++ b/api/api/v1/accounts/:id/following.ts @@ -1,16 +1,12 @@ -import { - accountNotFound, - apiRoute, - auth, - reusedResponses, - withUserParam, -} from "@/api"; +import { apiRoute, auth, withUserParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Timeline } from "@versia/kit/db"; import { Users } from "@versia/kit/tables"; import { and, gt, gte, lt, sql } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; + const route = createRoute({ method: "get", path: "/api/v1/accounts/{id}/following", @@ -79,8 +75,8 @@ const route = createRoute({ }), }), }, - 404: accountNotFound, - 422: reusedResponses[422], + 404: ApiError.accountNotFound().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/accounts/:id/index.ts b/api/api/v1/accounts/:id/index.ts index e11bf303..222d4c93 100644 --- a/api/api/v1/accounts/:id/index.ts +++ b/api/api/v1/accounts/:id/index.ts @@ -1,13 +1,8 @@ -import { - accountNotFound, - apiRoute, - auth, - reusedResponses, - withUserParam, -} from "@/api"; +import { apiRoute, auth, withUserParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -40,8 +35,8 @@ const route = createRoute({ }, }, }, - 404: accountNotFound, - 422: reusedResponses[422], + 404: ApiError.accountNotFound().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/accounts/:id/mute.ts b/api/api/v1/accounts/:id/mute.ts index b627cd86..18db3c62 100644 --- a/api/api/v1/accounts/:id/mute.ts +++ b/api/api/v1/accounts/:id/mute.ts @@ -1,10 +1,4 @@ -import { - accountNotFound, - apiRoute, - auth, - reusedResponses, - withUserParam, -} from "@/api"; +import { apiRoute, auth, withUserParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -12,6 +6,7 @@ import { } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Relationship } from "@versia/kit/db"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "post", @@ -71,8 +66,9 @@ const route = createRoute({ }, }, }, - 404: accountNotFound, - ...reusedResponses, + 404: ApiError.accountNotFound().schema, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/accounts/:id/note.ts b/api/api/v1/accounts/:id/note.ts index 0f8193f5..79621cd5 100644 --- a/api/api/v1/accounts/:id/note.ts +++ b/api/api/v1/accounts/:id/note.ts @@ -1,10 +1,4 @@ -import { - accountNotFound, - apiRoute, - auth, - reusedResponses, - withUserParam, -} from "@/api"; +import { apiRoute, auth, withUserParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -12,6 +6,7 @@ import { } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Relationship } from "@versia/kit/db"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "post", @@ -61,8 +56,9 @@ const route = createRoute({ }, }, }, - 404: accountNotFound, - ...reusedResponses, + 404: ApiError.accountNotFound().schema, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/accounts/:id/refetch.ts b/api/api/v1/accounts/:id/refetch.ts index 516ea113..662c9d9e 100644 --- a/api/api/v1/accounts/:id/refetch.ts +++ b/api/api/v1/accounts/:id/refetch.ts @@ -1,15 +1,8 @@ -import { - accountNotFound, - apiRoute, - auth, - reusedResponses, - withUserParam, -} from "@/api"; +import { apiRoute, auth, withUserParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { ApiError } from "~/classes/errors/api-error"; -import { ErrorSchema } from "~/types/api"; const route = createRoute({ method: "post", @@ -43,12 +36,13 @@ const route = createRoute({ description: "User is local", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, - 404: accountNotFound, - ...reusedResponses, + 404: ApiError.accountNotFound().schema, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/accounts/:id/remove_from_followers.ts b/api/api/v1/accounts/:id/remove_from_followers.ts index 1fe32a9d..99bae5ba 100644 --- a/api/api/v1/accounts/:id/remove_from_followers.ts +++ b/api/api/v1/accounts/:id/remove_from_followers.ts @@ -1,10 +1,4 @@ -import { - accountNotFound, - apiRoute, - auth, - reusedResponses, - withUserParam, -} from "@/api"; +import { apiRoute, auth, withUserParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -12,6 +6,7 @@ import { } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Relationship } from "@versia/kit/db"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "post", @@ -48,8 +43,9 @@ const route = createRoute({ }, }, }, - 404: accountNotFound, - ...reusedResponses, + 404: ApiError.accountNotFound().schema, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/accounts/:id/roles/:role_id/index.ts b/api/api/v1/accounts/:id/roles/:role_id/index.ts index c090a11b..8f918a87 100644 --- a/api/api/v1/accounts/:id/roles/:role_id/index.ts +++ b/api/api/v1/accounts/:id/roles/:role_id/index.ts @@ -7,7 +7,6 @@ import { import { RolePermission } from "@versia/client/schemas"; import { Role } from "@versia/kit/db"; import { ApiError } from "~/classes/errors/api-error"; -import { ErrorSchema } from "~/types/api"; const routePost = createRoute({ method: "post", @@ -31,22 +30,8 @@ const routePost = createRoute({ 204: { description: "Role assigned", }, - 404: { - description: "Role not found", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, - }, - 403: { - description: "Forbidden", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, - }, + 404: ApiError.roleNotFound().schema, + 403: ApiError.forbidden().schema, }, }); @@ -71,22 +56,8 @@ const routeDelete = createRoute({ 204: { description: "Role removed", }, - 404: { - description: "Role not found", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, - }, - 403: { - description: "Forbidden", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, - }, + 404: ApiError.roleNotFound().schema, + 403: ApiError.forbidden().schema, }, }); @@ -99,7 +70,7 @@ export default apiRoute((app) => { const role = await Role.fromId(role_id); if (!role) { - throw new ApiError(404, "Role not found"); + throw ApiError.roleNotFound(); } // Priority check const userRoles = await Role.getUserRoles(user.id, user.data.isAdmin); @@ -129,7 +100,7 @@ export default apiRoute((app) => { const role = await Role.fromId(role_id); if (!role) { - throw new ApiError(404, "Role not found"); + throw ApiError.roleNotFound(); } // Priority check diff --git a/api/api/v1/accounts/:id/statuses.ts b/api/api/v1/accounts/:id/statuses.ts index 3cd9aa28..e1ee1ffc 100644 --- a/api/api/v1/accounts/:id/statuses.ts +++ b/api/api/v1/accounts/:id/statuses.ts @@ -1,10 +1,4 @@ -import { - accountNotFound, - apiRoute, - auth, - reusedResponses, - withUserParam, -} from "@/api"; +import { apiRoute, auth, withUserParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -15,6 +9,7 @@ import { RolePermission } from "@versia/client/schemas"; import { Timeline } from "@versia/kit/db"; import { Notes } from "@versia/kit/tables"; import { and, eq, gt, gte, inArray, isNull, lt, or, sql } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -87,8 +82,8 @@ const route = createRoute({ }, }, }, - 404: accountNotFound, - 422: reusedResponses[422], + 404: ApiError.accountNotFound().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/accounts/:id/unblock.ts b/api/api/v1/accounts/:id/unblock.ts index 399c6c26..f2b4377a 100644 --- a/api/api/v1/accounts/:id/unblock.ts +++ b/api/api/v1/accounts/:id/unblock.ts @@ -1,10 +1,4 @@ -import { - accountNotFound, - apiRoute, - auth, - reusedResponses, - withUserParam, -} from "@/api"; +import { apiRoute, auth, withUserParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -12,6 +6,7 @@ import { } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Relationship } from "@versia/kit/db"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "post", @@ -48,8 +43,9 @@ const route = createRoute({ }, }, }, - 404: accountNotFound, - ...reusedResponses, + 404: ApiError.accountNotFound().schema, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/accounts/:id/unfollow.ts b/api/api/v1/accounts/:id/unfollow.ts index 718d39db..29f7f355 100644 --- a/api/api/v1/accounts/:id/unfollow.ts +++ b/api/api/v1/accounts/:id/unfollow.ts @@ -1,10 +1,4 @@ -import { - accountNotFound, - apiRoute, - auth, - reusedResponses, - withUserParam, -} from "@/api"; +import { apiRoute, auth, withUserParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -12,6 +6,7 @@ import { } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Relationship } from "@versia/kit/db"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "post", @@ -48,8 +43,9 @@ const route = createRoute({ }, }, }, - 404: accountNotFound, - ...reusedResponses, + 404: ApiError.accountNotFound().schema, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/accounts/:id/unmute.ts b/api/api/v1/accounts/:id/unmute.ts index 3332c530..2de4a3c8 100644 --- a/api/api/v1/accounts/:id/unmute.ts +++ b/api/api/v1/accounts/:id/unmute.ts @@ -1,10 +1,4 @@ -import { - accountNotFound, - apiRoute, - auth, - reusedResponses, - withUserParam, -} from "@/api"; +import { apiRoute, auth, withUserParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -12,6 +6,7 @@ import { } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Relationship } from "@versia/kit/db"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "post", @@ -47,8 +42,9 @@ const route = createRoute({ }, }, }, - 404: accountNotFound, - ...reusedResponses, + 404: ApiError.accountNotFound().schema, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/accounts/:id/unpin.ts b/api/api/v1/accounts/:id/unpin.ts index aaa0e7b1..ec4ab0e1 100644 --- a/api/api/v1/accounts/:id/unpin.ts +++ b/api/api/v1/accounts/:id/unpin.ts @@ -1,10 +1,4 @@ -import { - accountNotFound, - apiRoute, - auth, - reusedResponses, - withUserParam, -} from "@/api"; +import { apiRoute, auth, withUserParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -12,6 +6,7 @@ import { } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Relationship } from "@versia/kit/db"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "post", @@ -48,8 +43,9 @@ const route = createRoute({ }, }, }, - 404: accountNotFound, - ...reusedResponses, + 404: ApiError.accountNotFound().schema, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/accounts/familiar_followers/index.ts b/api/api/v1/accounts/familiar_followers/index.ts index fc82239f..b615e0b4 100644 --- a/api/api/v1/accounts/familiar_followers/index.ts +++ b/api/api/v1/accounts/familiar_followers/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, auth, qsQuery, reusedResponses } from "@/api"; +import { apiRoute, auth, qsQuery } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -8,6 +8,7 @@ import { RolePermission } from "@versia/client/schemas"; import { User, db } from "@versia/kit/db"; import type { Users } from "@versia/kit/tables"; import { type InferSelectModel, sql } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -53,7 +54,8 @@ const route = createRoute({ }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/accounts/index.ts b/api/api/v1/accounts/index.ts index 2c5f083c..e7f5ab17 100644 --- a/api/api/v1/accounts/index.ts +++ b/api/api/v1/accounts/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, auth, jsonOrForm, reusedResponses } from "@/api"; +import { apiRoute, auth, jsonOrForm } from "@/api"; import { tempmailDomains } from "@/tempmail"; import { createRoute, z } from "@hono/zod-openapi"; import { zBoolean } from "@versia/client/schemas"; @@ -76,7 +76,7 @@ const route = createRoute({ 200: { description: "Token for the created account", }, - 401: reusedResponses[401], + 401: ApiError.missingAuthentication().schema, 422: { description: "Validation failed", content: { diff --git a/api/api/v1/accounts/lookup/index.ts b/api/api/v1/accounts/lookup/index.ts index e70e982e..d5c8919f 100644 --- a/api/api/v1/accounts/lookup/index.ts +++ b/api/api/v1/accounts/lookup/index.ts @@ -1,10 +1,4 @@ -import { - accountNotFound, - apiRoute, - auth, - parseUserAddress, - reusedResponses, -} from "@/api"; +import { apiRoute, auth, parseUserAddress } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; @@ -43,8 +37,8 @@ const route = createRoute({ }, }, }, - 404: accountNotFound, - 422: reusedResponses[422], + 404: ApiError.accountNotFound().schema, + 422: ApiError.validationFailed().schema, }, }); @@ -97,7 +91,7 @@ export default apiRoute((app) => const uri = await User.webFinger(manager, username, domain); if (!uri) { - throw new ApiError(404, "Account not found"); + throw ApiError.accountNotFound(); } const foundAccount = await User.resolve(uri); @@ -106,6 +100,6 @@ export default apiRoute((app) => return context.json(foundAccount.toApi(), 200); } - throw new ApiError(404, "Account not found"); + throw ApiError.accountNotFound(); }), ); diff --git a/api/api/v1/accounts/relationships/index.ts b/api/api/v1/accounts/relationships/index.ts index 00b8d140..312d3146 100644 --- a/api/api/v1/accounts/relationships/index.ts +++ b/api/api/v1/accounts/relationships/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, auth, qsQuery, reusedResponses } from "@/api"; +import { apiRoute, auth, qsQuery } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -7,6 +7,7 @@ import { } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Relationship } from "@versia/kit/db"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -57,7 +58,8 @@ const route = createRoute({ }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/accounts/update_credentials/index.ts b/api/api/v1/accounts/update_credentials/index.ts index c2af8cc1..606e2807 100644 --- a/api/api/v1/accounts/update_credentials/index.ts +++ b/api/api/v1/accounts/update_credentials/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, auth, jsonOrForm, reusedResponses } from "@/api"; +import { apiRoute, auth, jsonOrForm } from "@/api"; import { mergeAndDeduplicate } from "@/lib"; import { sanitizedHtmlStrip } from "@/sanitization"; import { createRoute, z } from "@hono/zod-openapi"; @@ -162,7 +162,8 @@ const route = createRoute({ }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/accounts/verify_credentials/index.ts b/api/api/v1/accounts/verify_credentials/index.ts index 56212264..5278d175 100644 --- a/api/api/v1/accounts/verify_credentials/index.ts +++ b/api/api/v1/accounts/verify_credentials/index.ts @@ -1,6 +1,7 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute } from "@hono/zod-openapi"; import { Account } from "@versia/client/schemas"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -28,7 +29,8 @@ const route = createRoute({ }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/apps/index.ts b/api/api/v1/apps/index.ts index 32790b6e..20ed7d96 100644 --- a/api/api/v1/apps/index.ts +++ b/api/api/v1/apps/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, jsonOrForm, reusedResponses } from "@/api"; +import { apiRoute, jsonOrForm } from "@/api"; import { randomString } from "@/math"; import { createRoute, z } from "@hono/zod-openapi"; import { @@ -6,6 +6,7 @@ import { CredentialApplication as CredentialApplicationSchema, } from "@versia/client/schemas"; import { Application } from "@versia/kit/db"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "post", @@ -55,7 +56,7 @@ const route = createRoute({ }, }, }, - 422: reusedResponses[422], + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/apps/verify_credentials/index.ts b/api/api/v1/apps/verify_credentials/index.ts index 20382dc6..ac66eb62 100644 --- a/api/api/v1/apps/verify_credentials/index.ts +++ b/api/api/v1/apps/verify_credentials/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute } from "@hono/zod-openapi"; import { Application as ApplicationSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; @@ -30,7 +30,8 @@ const route = createRoute({ }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); @@ -38,16 +39,12 @@ export default apiRoute((app) => app.openapi(route, async (context) => { const { token } = context.get("auth"); - if (!token) { - throw new ApiError(401, "Unauthorized"); - } - const application = await Application.getFromToken( token.data.accessToken, ); if (!application) { - throw new ApiError(401, "Application not found"); + throw ApiError.applicationNotFound(); } return context.json(application.toApi(), 200); diff --git a/api/api/v1/blocks/index.ts b/api/api/v1/blocks/index.ts index 1482f386..e40f1b7c 100644 --- a/api/api/v1/blocks/index.ts +++ b/api/api/v1/blocks/index.ts @@ -1,10 +1,12 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Timeline } from "@versia/kit/db"; import { Users } from "@versia/kit/tables"; import { and, gt, gte, lt, sql } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; + const route = createRoute({ method: "get", path: "/api/v1/blocks", @@ -65,7 +67,8 @@ const route = createRoute({ }), }), }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/challenges/index.ts b/api/api/v1/challenges/index.ts index 359367f3..9a27cd82 100644 --- a/api/api/v1/challenges/index.ts +++ b/api/api/v1/challenges/index.ts @@ -4,7 +4,6 @@ import { createRoute } from "@hono/zod-openapi"; import { Challenge } from "@versia/client/schemas"; import { ApiError } from "~/classes/errors/api-error"; import { config } from "~/config.ts"; -import { ErrorSchema } from "~/types/api"; const route = createRoute({ method: "post", @@ -30,7 +29,7 @@ const route = createRoute({ description: "Challenges are disabled", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, diff --git a/api/api/v1/custom_emojis/index.ts b/api/api/v1/custom_emojis/index.ts index 63dcbdfa..61f1b437 100644 --- a/api/api/v1/custom_emojis/index.ts +++ b/api/api/v1/custom_emojis/index.ts @@ -1,10 +1,11 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { CustomEmoji as CustomEmojiSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Emoji } from "@versia/kit/db"; import { Emojis } from "@versia/kit/tables"; import { and, eq, isNull, or } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -30,7 +31,7 @@ const route = createRoute({ }, }, }, - 422: reusedResponses[422], + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/emojis/:id/index.ts b/api/api/v1/emojis/:id/index.ts index e5e8d3c6..2b65e4cf 100644 --- a/api/api/v1/emojis/:id/index.ts +++ b/api/api/v1/emojis/:id/index.ts @@ -1,17 +1,10 @@ -import { - apiRoute, - auth, - jsonOrForm, - reusedResponses, - withEmojiParam, -} from "@/api"; +import { apiRoute, auth, jsonOrForm, withEmojiParam } from "@/api"; import { mimeLookup } from "@/content_types"; import { createRoute, z } from "@hono/zod-openapi"; import { CustomEmoji as CustomEmojiSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { ApiError } from "~/classes/errors/api-error"; import { config } from "~/config.ts"; -import { ErrorSchema } from "~/types/api"; const schema = z .object({ @@ -72,11 +65,12 @@ const routeGet = createRoute({ description: "Emoji not found", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); @@ -128,7 +122,7 @@ const routePatch = createRoute({ description: "Insufficient permissions", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, @@ -136,11 +130,12 @@ const routePatch = createRoute({ description: "Emoji not found", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); @@ -169,14 +164,7 @@ const routeDelete = createRoute({ 204: { description: "Emoji deleted", }, - 404: { - description: "Emoji not found", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, - }, + 404: ApiError.emojiNotFound().schema, }, }); @@ -190,7 +178,7 @@ export default apiRoute((app) => { !user.hasPermission(RolePermission.ManageEmojis) && emoji.data.ownerId !== user.data.id ) { - throw new ApiError(404, "Emoji not found"); + throw ApiError.emojiNotFound(); } return context.json(emoji.toApi(), 200); diff --git a/api/api/v1/emojis/index.ts b/api/api/v1/emojis/index.ts index c9f4ed0a..7643cd5c 100644 --- a/api/api/v1/emojis/index.ts +++ b/api/api/v1/emojis/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, auth, jsonOrForm, reusedResponses } from "@/api"; +import { apiRoute, auth, jsonOrForm } from "@/api"; import { mimeLookup } from "@/content_types"; import { createRoute, z } from "@hono/zod-openapi"; import { CustomEmoji as CustomEmojiSchema } from "@versia/client/schemas"; @@ -73,7 +73,8 @@ const route = createRoute({ }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/favourites/index.ts b/api/api/v1/favourites/index.ts index b0e31550..37f17dd2 100644 --- a/api/api/v1/favourites/index.ts +++ b/api/api/v1/favourites/index.ts @@ -1,10 +1,11 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Status as StatusSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Timeline } from "@versia/kit/db"; import { Notes } from "@versia/kit/tables"; import { and, gt, gte, lt, sql } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -65,7 +66,8 @@ const route = createRoute({ }), }), }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/follow_requests/:account_id/authorize.ts b/api/api/v1/follow_requests/:account_id/authorize.ts index 678fd593..f0df124f 100644 --- a/api/api/v1/follow_requests/:account_id/authorize.ts +++ b/api/api/v1/follow_requests/:account_id/authorize.ts @@ -1,4 +1,4 @@ -import { accountNotFound, apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -37,8 +37,9 @@ const route = createRoute({ }, }, }, - 404: accountNotFound, - ...reusedResponses, + 404: ApiError.accountNotFound().schema, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); @@ -51,7 +52,7 @@ export default apiRoute((app) => const account = await User.fromId(account_id); if (!account) { - throw new ApiError(404, "Account not found"); + throw ApiError.accountNotFound(); } const oppositeRelationship = await Relationship.fromOwnerAndSubject( diff --git a/api/api/v1/follow_requests/:account_id/reject.ts b/api/api/v1/follow_requests/:account_id/reject.ts index 5ce0e0d6..33e17cc9 100644 --- a/api/api/v1/follow_requests/:account_id/reject.ts +++ b/api/api/v1/follow_requests/:account_id/reject.ts @@ -1,4 +1,4 @@ -import { accountNotFound, apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -37,8 +37,9 @@ const route = createRoute({ }, }, }, - 404: accountNotFound, - ...reusedResponses, + 404: ApiError.accountNotFound().schema, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); @@ -51,7 +52,7 @@ export default apiRoute((app) => const account = await User.fromId(account_id); if (!account) { - throw new ApiError(404, "Account not found"); + throw ApiError.accountNotFound(); } const oppositeRelationship = await Relationship.fromOwnerAndSubject( diff --git a/api/api/v1/follow_requests/index.ts b/api/api/v1/follow_requests/index.ts index d1c35c31..56a20a5e 100644 --- a/api/api/v1/follow_requests/index.ts +++ b/api/api/v1/follow_requests/index.ts @@ -1,10 +1,11 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Timeline } from "@versia/kit/db"; import { Users } from "@versia/kit/tables"; import { and, gt, gte, lt, sql } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -66,7 +67,8 @@ const route = createRoute({ }), }), }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/markers/index.ts b/api/api/v1/markers/index.ts index d5fbd126..33f880fe 100644 --- a/api/api/v1/markers/index.ts +++ b/api/api/v1/markers/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Marker as MarkerSchema, @@ -9,6 +9,7 @@ import { RolePermission } from "@versia/client/schemas"; import { db } from "@versia/kit/db"; import { Markers } from "@versia/kit/tables"; import { type SQL, and, eq } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; const MarkerResponseSchema = z.object({ notifications: MarkerSchema.optional(), @@ -52,7 +53,8 @@ const routeGet = createRoute({ }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); @@ -95,7 +97,8 @@ const routePost = createRoute({ }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/media/:id/index.ts b/api/api/v1/media/:id/index.ts index 6b4246a9..a5a0dfa2 100644 --- a/api/api/v1/media/:id/index.ts +++ b/api/api/v1/media/:id/index.ts @@ -1,10 +1,9 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Attachment as AttachmentSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Media } from "@versia/kit/db"; import { ApiError } from "~/classes/errors/api-error"; -import { ErrorSchema } from "~/types/api"; const routePut = createRoute({ method: "put", @@ -63,11 +62,12 @@ const routePut = createRoute({ description: "Attachment not found", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); @@ -105,11 +105,12 @@ const routeGet = createRoute({ description: "Attachment not found", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); @@ -120,7 +121,7 @@ export default apiRoute((app) => { const media = await Media.fromId(id); if (!media) { - throw new ApiError(404, "Media not found"); + throw ApiError.mediaNotFound(); } const { description, thumbnail: thumbnailFile } = @@ -145,7 +146,7 @@ export default apiRoute((app) => { const attachment = await Media.fromId(id); if (!attachment) { - throw new ApiError(404, "Media not found"); + throw ApiError.mediaNotFound(); } return context.json(attachment.toApi(), 200); diff --git a/api/api/v1/media/index.ts b/api/api/v1/media/index.ts index e005efc9..f49317cb 100644 --- a/api/api/v1/media/index.ts +++ b/api/api/v1/media/index.ts @@ -1,9 +1,9 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Attachment as AttachmentSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Media } from "@versia/kit/db"; -import { ErrorSchema } from "~/types/api"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "post", @@ -67,7 +67,7 @@ const route = createRoute({ description: "File too large", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, @@ -75,11 +75,12 @@ const route = createRoute({ description: "Disallowed file type", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/mutes/index.ts b/api/api/v1/mutes/index.ts index b489f427..859f07c4 100644 --- a/api/api/v1/mutes/index.ts +++ b/api/api/v1/mutes/index.ts @@ -1,10 +1,11 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Timeline } from "@versia/kit/db"; import { Users } from "@versia/kit/tables"; import { and, gt, gte, lt, sql } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -66,7 +67,8 @@ const route = createRoute({ }), }), }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/notifications/:id/dismiss.ts b/api/api/v1/notifications/:id/dismiss.ts index 776b31f0..092df632 100644 --- a/api/api/v1/notifications/:id/dismiss.ts +++ b/api/api/v1/notifications/:id/dismiss.ts @@ -1,4 +1,4 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Notification as NotificationSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; @@ -30,7 +30,8 @@ const route = createRoute({ 200: { description: "Notification with given ID successfully dismissed", }, - 401: reusedResponses[401], + 401: ApiError.missingAuthentication().schema, + 404: ApiError.notificationNotFound().schema, }, }); @@ -43,7 +44,7 @@ export default apiRoute((app) => const notification = await Notification.fromId(id); if (!notification || notification.data.notifiedId !== user.id) { - throw new ApiError(404, "Notification not found"); + throw ApiError.notificationNotFound(); } await notification.update({ diff --git a/api/api/v1/notifications/:id/index.ts b/api/api/v1/notifications/:id/index.ts index f2ae5803..da8f1e21 100644 --- a/api/api/v1/notifications/:id/index.ts +++ b/api/api/v1/notifications/:id/index.ts @@ -1,10 +1,9 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Notification as NotificationSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Notification } from "@versia/kit/db"; import { ApiError } from "~/classes/errors/api-error"; -import { ErrorSchema } from "~/types/api"; const route = createRoute({ method: "get", @@ -36,15 +35,8 @@ const route = createRoute({ }, }, }, - 404: { - description: "Notification not found", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, - }, - 401: reusedResponses[401], + 404: ApiError.notificationNotFound().schema, + 401: ApiError.missingAuthentication().schema, }, }); @@ -57,7 +49,7 @@ export default apiRoute((app) => const notification = await Notification.fromId(id, user.id); if (!notification || notification.data.notifiedId !== user.id) { - throw new ApiError(404, "Notification not found"); + throw ApiError.notificationNotFound(); } return context.json(await notification.toApi(), 200); diff --git a/api/api/v1/notifications/clear/index.ts b/api/api/v1/notifications/clear/index.ts index 90e0af02..b9a17c75 100644 --- a/api/api/v1/notifications/clear/index.ts +++ b/api/api/v1/notifications/clear/index.ts @@ -1,6 +1,7 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute } from "@hono/zod-openapi"; import { RolePermission } from "@versia/client/schemas"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "post", @@ -22,7 +23,7 @@ const route = createRoute({ 200: { description: "Notifications successfully cleared.", }, - 401: reusedResponses[401], + 401: ApiError.missingAuthentication().schema, }, }); diff --git a/api/api/v1/notifications/destroy_multiple/index.ts b/api/api/v1/notifications/destroy_multiple/index.ts index 0ad3be86..a1b90a1b 100644 --- a/api/api/v1/notifications/destroy_multiple/index.ts +++ b/api/api/v1/notifications/destroy_multiple/index.ts @@ -1,6 +1,7 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { RolePermission } from "@versia/client/schemas"; +import { ApiError } from "~/classes/errors/api-error"; const schemas = { query: z.object({ @@ -26,7 +27,7 @@ const route = createRoute({ 200: { description: "Notifications dismissed", }, - 401: reusedResponses[401], + 401: ApiError.missingAuthentication().schema, }, }); diff --git a/api/api/v1/notifications/index.ts b/api/api/v1/notifications/index.ts index a9dd56bb..1644caab 100644 --- a/api/api/v1/notifications/index.ts +++ b/api/api/v1/notifications/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -9,6 +9,7 @@ import { RolePermission } from "@versia/client/schemas"; import { Timeline } from "@versia/kit/db"; import { Notifications } from "@versia/kit/tables"; import { and, eq, gt, gte, inArray, lt, not, sql } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -90,7 +91,8 @@ const route = createRoute({ }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/profile/avatar.ts b/api/api/v1/profile/avatar.ts index 8a257b3b..ba3bbfa7 100644 --- a/api/api/v1/profile/avatar.ts +++ b/api/api/v1/profile/avatar.ts @@ -1,7 +1,8 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute } from "@hono/zod-openapi"; import { Account } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "delete", @@ -30,7 +31,8 @@ const route = createRoute({ }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/profile/header.ts b/api/api/v1/profile/header.ts index fe13281e..724c278f 100644 --- a/api/api/v1/profile/header.ts +++ b/api/api/v1/profile/header.ts @@ -1,7 +1,8 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute } from "@hono/zod-openapi"; import { Account } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "delete", @@ -29,7 +30,8 @@ const route = createRoute({ }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/push/subscription/index.delete.ts b/api/api/v1/push/subscription/index.delete.ts index e9a77f1e..05b6f689 100644 --- a/api/api/v1/push/subscription/index.delete.ts +++ b/api/api/v1/push/subscription/index.delete.ts @@ -1,4 +1,4 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { RolePermission } from "@versia/client/schemas"; import { PushSubscription } from "@versia/kit/db"; @@ -32,7 +32,8 @@ export default apiRoute((app) => }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }), async (context) => { @@ -41,10 +42,7 @@ export default apiRoute((app) => const ps = await PushSubscription.fromToken(token); if (!ps) { - throw new ApiError( - 404, - "No push subscription associated with this access token", - ); + throw ApiError.pushSubscriptionNotFound(); } await ps.delete(); diff --git a/api/api/v1/push/subscription/index.get.ts b/api/api/v1/push/subscription/index.get.ts index b5e00960..3f18acdb 100644 --- a/api/api/v1/push/subscription/index.get.ts +++ b/api/api/v1/push/subscription/index.get.ts @@ -1,4 +1,4 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute } from "@hono/zod-openapi"; import { WebPushSubscription as WebPushSubscriptionSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; @@ -33,7 +33,8 @@ export default apiRoute((app) => }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }), async (context) => { @@ -42,10 +43,7 @@ export default apiRoute((app) => const ps = await PushSubscription.fromToken(token); if (!ps) { - throw new ApiError( - 404, - "No push subscription associated with this access token", - ); + throw ApiError.pushSubscriptionNotFound(); } return context.json(ps.toApi(), 200); diff --git a/api/api/v1/push/subscription/index.post.ts b/api/api/v1/push/subscription/index.post.ts index ed498e74..dbe3a310 100644 --- a/api/api/v1/push/subscription/index.post.ts +++ b/api/api/v1/push/subscription/index.post.ts @@ -1,4 +1,4 @@ -import { apiRoute, reusedResponses } from "@/api"; +import { apiRoute } from "@/api"; import { auth, jsonOrForm } from "@/api"; import { createRoute } from "@hono/zod-openapi"; import { @@ -7,6 +7,7 @@ import { } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { PushSubscription } from "@versia/kit/db"; +import { ApiError } from "~/classes/errors/api-error"; export default apiRoute((app) => app.openapi( @@ -47,7 +48,8 @@ export default apiRoute((app) => }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }), async (context) => { diff --git a/api/api/v1/push/subscription/index.put.ts b/api/api/v1/push/subscription/index.put.ts index f36fe020..77be61b8 100644 --- a/api/api/v1/push/subscription/index.put.ts +++ b/api/api/v1/push/subscription/index.put.ts @@ -1,4 +1,4 @@ -import { apiRoute, auth, jsonOrForm, reusedResponses } from "@/api"; +import { apiRoute, auth, jsonOrForm } from "@/api"; import { createRoute } from "@hono/zod-openapi"; import { WebPushSubscriptionInput, @@ -49,7 +49,8 @@ export default apiRoute((app) => }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }), async (context) => { @@ -59,10 +60,7 @@ export default apiRoute((app) => const ps = await PushSubscription.fromToken(token); if (!ps) { - throw new ApiError( - 404, - "No push subscription associated with this access token", - ); + throw ApiError.pushSubscriptionNotFound(); } if ( diff --git a/api/api/v1/roles/:id/index.ts b/api/api/v1/roles/:id/index.ts index 818a0872..c4254cc6 100644 --- a/api/api/v1/roles/:id/index.ts +++ b/api/api/v1/roles/:id/index.ts @@ -4,7 +4,6 @@ import { Role as RoleSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Role } from "@versia/kit/db"; import { ApiError } from "~/classes/errors/api-error"; -import { ErrorSchema } from "~/types/api"; const routeGet = createRoute({ method: "get", @@ -29,15 +28,8 @@ const routeGet = createRoute({ }, }, }, - - 404: { - description: "Role not found", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, - }, + 404: ApiError.roleNotFound().schema, + 403: ApiError.forbidden().schema, }, }); @@ -67,23 +59,8 @@ const routePatch = createRoute({ 204: { description: "Role updated", }, - - 404: { - description: "Role not found", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, - }, - 403: { - description: "Forbidden", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, - }, + 404: ApiError.roleNotFound().schema, + 403: ApiError.forbidden().schema, }, }); @@ -106,23 +83,8 @@ const routeDelete = createRoute({ 204: { description: "Role deleted", }, - - 404: { - description: "Role not found", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, - }, - 403: { - description: "Forbidden", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, - }, + 404: ApiError.roleNotFound().schema, + 403: ApiError.forbidden().schema, }, }); @@ -133,7 +95,7 @@ export default apiRoute((app) => { const role = await Role.fromId(id); if (!role) { - throw new ApiError(404, "Role not found"); + throw ApiError.roleNotFound(); } return context.json(role.toApi(), 200); @@ -148,7 +110,7 @@ export default apiRoute((app) => { const role = await Role.fromId(id); if (!role) { - throw new ApiError(404, "Role not found"); + throw ApiError.roleNotFound(); } // Priority check @@ -201,7 +163,7 @@ export default apiRoute((app) => { const role = await Role.fromId(id); if (!role) { - throw new ApiError(404, "Role not found"); + throw ApiError.roleNotFound(); } // Priority check diff --git a/api/api/v1/roles/index.ts b/api/api/v1/roles/index.ts index 22bc6f5d..7842166a 100644 --- a/api/api/v1/roles/index.ts +++ b/api/api/v1/roles/index.ts @@ -4,7 +4,6 @@ import { Role as RoleSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Role } from "@versia/kit/db"; import { ApiError } from "~/classes/errors/api-error"; -import { ErrorSchema } from "~/types/api"; const routeGet = createRoute({ method: "get", @@ -60,7 +59,7 @@ const routePost = createRoute({ description: "Forbidden", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, diff --git a/api/api/v1/statuses/:id/context.ts b/api/api/v1/statuses/:id/context.ts index 01c47733..992fa3ce 100644 --- a/api/api/v1/statuses/:id/context.ts +++ b/api/api/v1/statuses/:id/context.ts @@ -1,16 +1,11 @@ -import { - apiRoute, - auth, - noteNotFound, - reusedResponses, - withNoteParam, -} from "@/api"; +import { apiRoute, auth, withNoteParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Context as ContextSchema, Status as StatusSchema, } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -42,8 +37,8 @@ const route = createRoute({ }, }, }, - 404: noteNotFound, - 401: reusedResponses[401], + 404: ApiError.noteNotFound().schema, + 401: ApiError.missingAuthentication().schema, }, }); diff --git a/api/api/v1/statuses/:id/favourite.ts b/api/api/v1/statuses/:id/favourite.ts index 45eaf9c7..bb89ee52 100644 --- a/api/api/v1/statuses/:id/favourite.ts +++ b/api/api/v1/statuses/:id/favourite.ts @@ -1,13 +1,8 @@ -import { - apiRoute, - auth, - noteNotFound, - reusedResponses, - withNoteParam, -} from "@/api"; +import { apiRoute, auth, withNoteParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Status as StatusSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "post", @@ -42,8 +37,8 @@ const route = createRoute({ }, }, }, - 404: noteNotFound, - 401: reusedResponses[401], + 404: ApiError.noteNotFound().schema, + 401: ApiError.missingAuthentication().schema, }, }); diff --git a/api/api/v1/statuses/:id/favourited_by.ts b/api/api/v1/statuses/:id/favourited_by.ts index 875c99d7..c1323ec0 100644 --- a/api/api/v1/statuses/:id/favourited_by.ts +++ b/api/api/v1/statuses/:id/favourited_by.ts @@ -1,10 +1,4 @@ -import { - apiRoute, - auth, - noteNotFound, - reusedResponses, - withNoteParam, -} from "@/api"; +import { apiRoute, auth, withNoteParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -14,6 +8,7 @@ import { RolePermission } from "@versia/client/schemas"; import { Timeline } from "@versia/kit/db"; import { Users } from "@versia/kit/tables"; import { and, gt, gte, lt, sql } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -81,8 +76,9 @@ const route = createRoute({ }), }), }, - 404: noteNotFound, - ...reusedResponses, + 404: ApiError.noteNotFound().schema, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/statuses/:id/index.test.ts b/api/api/v1/statuses/:id/index.test.ts index 97cb177b..6a98f25a 100644 --- a/api/api/v1/statuses/:id/index.test.ts +++ b/api/api/v1/statuses/:id/index.test.ts @@ -29,13 +29,13 @@ describe("GET /api/v1/statuses/:id", () => { expect(raw.status).toBe(404); }); - test("should return 401 when trying to delete status that is not yours", async () => { + test("should forbid deleting status that is not yours", async () => { await using client = await generateClient(users[1]); const { ok, raw } = await client.deleteStatus(statuses[0].id); expect(ok).toBe(false); - expect(raw.status).toBe(401); + expect(raw.status).toBe(403); }); test("should delete status", async () => { diff --git a/api/api/v1/statuses/:id/index.ts b/api/api/v1/statuses/:id/index.ts index 746e49f7..1b4d2aba 100644 --- a/api/api/v1/statuses/:id/index.ts +++ b/api/api/v1/statuses/:id/index.ts @@ -1,11 +1,4 @@ -import { - apiRoute, - auth, - jsonOrForm, - noteNotFound, - reusedResponses, - withNoteParam, -} from "@/api"; +import { apiRoute, auth, jsonOrForm, withNoteParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Attachment as AttachmentSchema, @@ -109,7 +102,7 @@ const routeGet = createRoute({ }, }, }, - 404: noteNotFound, + 404: ApiError.noteNotFound().schema, }, }); @@ -147,8 +140,8 @@ const routeDelete = createRoute({ }, }, }, - 404: noteNotFound, - 401: reusedResponses[401], + 404: ApiError.noteNotFound().schema, + 401: ApiError.missingAuthentication().schema, }, }); @@ -200,8 +193,10 @@ const routePut = createRoute({ }, }, }, - 404: noteNotFound, - ...reusedResponses, + 404: ApiError.noteNotFound().schema, + 403: ApiError.forbidden().schema, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); @@ -218,7 +213,7 @@ export default apiRoute((app) => { const note = context.get("note"); if (note.author.id !== user.id) { - throw new ApiError(401, "Unauthorized"); + throw ApiError.forbidden(); } // TODO: Delete and redraft @@ -234,7 +229,7 @@ export default apiRoute((app) => { const note = context.get("note"); if (note.author.id !== user.id) { - throw new ApiError(401, "Unauthorized"); + throw ApiError.forbidden(); } // TODO: Polls diff --git a/api/api/v1/statuses/:id/pin.ts b/api/api/v1/statuses/:id/pin.ts index 7e81c0c4..cb92121e 100644 --- a/api/api/v1/statuses/:id/pin.ts +++ b/api/api/v1/statuses/:id/pin.ts @@ -1,10 +1,4 @@ -import { - apiRoute, - auth, - noteNotFound, - reusedResponses, - withNoteParam, -} from "@/api"; +import { apiRoute, auth, withNoteParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Status as StatusSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; @@ -47,8 +41,9 @@ const route = createRoute({ }, }, }, - 404: noteNotFound, - 401: reusedResponses[401], + 404: ApiError.noteNotFound().schema, + 403: ApiError.forbidden().schema, + 401: ApiError.missingAuthentication().schema, }, }); @@ -58,7 +53,7 @@ export default apiRoute((app) => const note = context.get("note"); if (note.author.id !== user.id) { - throw new ApiError(401, "Unauthorized"); + throw ApiError.forbidden(); } if ( @@ -70,7 +65,7 @@ export default apiRoute((app) => ), }) ) { - throw new ApiError(422, "Already pinned"); + return context.json(await note.toApi(user), 200); } await user.pin(note); diff --git a/api/api/v1/statuses/:id/reblog.ts b/api/api/v1/statuses/:id/reblog.ts index 511dab53..7e68bb07 100644 --- a/api/api/v1/statuses/:id/reblog.ts +++ b/api/api/v1/statuses/:id/reblog.ts @@ -1,17 +1,11 @@ -import { - apiRoute, - auth, - jsonOrForm, - noteNotFound, - reusedResponses, - withNoteParam, -} from "@/api"; +import { apiRoute, auth, jsonOrForm, withNoteParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Status as StatusSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Note } from "@versia/kit/db"; import { Notes } from "@versia/kit/tables"; import { and, eq } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "post", @@ -70,8 +64,9 @@ const route = createRoute({ }, }, }, - 404: noteNotFound, - ...reusedResponses, + 404: ApiError.noteNotFound().schema, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/statuses/:id/reblogged_by.ts b/api/api/v1/statuses/:id/reblogged_by.ts index aabfd21a..3f76dec8 100644 --- a/api/api/v1/statuses/:id/reblogged_by.ts +++ b/api/api/v1/statuses/:id/reblogged_by.ts @@ -1,10 +1,4 @@ -import { - apiRoute, - auth, - noteNotFound, - reusedResponses, - withNoteParam, -} from "@/api"; +import { apiRoute, auth, withNoteParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -14,6 +8,7 @@ import { RolePermission } from "@versia/client/schemas"; import { Timeline } from "@versia/kit/db"; import { Users } from "@versia/kit/tables"; import { and, gt, gte, lt, sql } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -81,8 +76,9 @@ const route = createRoute({ }), }), }, - 404: noteNotFound, - ...reusedResponses, + 404: ApiError.noteNotFound().schema, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/statuses/:id/source.ts b/api/api/v1/statuses/:id/source.ts index 5cadb6f7..78e027fe 100644 --- a/api/api/v1/statuses/:id/source.ts +++ b/api/api/v1/statuses/:id/source.ts @@ -1,16 +1,11 @@ -import { - apiRoute, - auth, - noteNotFound, - reusedResponses, - withNoteParam, -} from "@/api"; +import { apiRoute, auth, withNoteParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Status as StatusSchema, StatusSource as StatusSourceSchema, } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -46,8 +41,8 @@ const route = createRoute({ }, }, }, - 404: noteNotFound, - 401: reusedResponses[401], + 404: ApiError.noteNotFound().schema, + 401: ApiError.missingAuthentication().schema, }, }); diff --git a/api/api/v1/statuses/:id/unfavourite.ts b/api/api/v1/statuses/:id/unfavourite.ts index fbe75875..edd46d21 100644 --- a/api/api/v1/statuses/:id/unfavourite.ts +++ b/api/api/v1/statuses/:id/unfavourite.ts @@ -1,13 +1,8 @@ -import { - apiRoute, - auth, - noteNotFound, - reusedResponses, - withNoteParam, -} from "@/api"; +import { apiRoute, auth, withNoteParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Status as StatusSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "post", @@ -42,8 +37,8 @@ const route = createRoute({ }, }, }, - 404: noteNotFound, - 401: reusedResponses[401], + 404: ApiError.noteNotFound().schema, + 401: ApiError.missingAuthentication().schema, }, }); diff --git a/api/api/v1/statuses/:id/unpin.ts b/api/api/v1/statuses/:id/unpin.ts index 85a4688b..e516d7c4 100644 --- a/api/api/v1/statuses/:id/unpin.ts +++ b/api/api/v1/statuses/:id/unpin.ts @@ -1,10 +1,4 @@ -import { - apiRoute, - auth, - noteNotFound, - reusedResponses, - withNoteParam, -} from "@/api"; +import { apiRoute, auth, withNoteParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Status as StatusSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; @@ -43,8 +37,9 @@ const route = createRoute({ }, }, }, - 404: noteNotFound, - 401: reusedResponses[401], + 404: ApiError.noteNotFound().schema, + 403: ApiError.forbidden().schema, + 401: ApiError.missingAuthentication().schema, }, }); @@ -54,13 +49,13 @@ export default apiRoute((app) => const note = context.get("note"); if (note.author.id !== user.id) { - throw new ApiError(401, "Unauthorized"); + throw ApiError.forbidden(); } await user.unpin(note); if (!note) { - throw new ApiError(404, "Note not found"); + throw ApiError.noteNotFound(); } return context.json(await note.toApi(user), 200); diff --git a/api/api/v1/statuses/:id/unreblog.ts b/api/api/v1/statuses/:id/unreblog.ts index d15ad201..2b8e53c1 100644 --- a/api/api/v1/statuses/:id/unreblog.ts +++ b/api/api/v1/statuses/:id/unreblog.ts @@ -1,10 +1,4 @@ -import { - apiRoute, - auth, - noteNotFound, - reusedResponses, - withNoteParam, -} from "@/api"; +import { apiRoute, auth, withNoteParam } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Status as StatusSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; @@ -46,8 +40,8 @@ const route = createRoute({ }, }, }, - 404: noteNotFound, - 401: reusedResponses[401], + 404: ApiError.noteNotFound().schema, + 401: ApiError.missingAuthentication().schema, }, }); @@ -74,7 +68,7 @@ export default apiRoute((app) => const newNote = await Note.fromId(id, user.id); if (!newNote) { - throw new ApiError(404, "Note not found"); + throw ApiError.noteNotFound(); } return context.json(await newNote.toApi(user), 200); diff --git a/api/api/v1/statuses/index.ts b/api/api/v1/statuses/index.ts index 3b36d310..033215a9 100644 --- a/api/api/v1/statuses/index.ts +++ b/api/api/v1/statuses/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, auth, jsonOrForm, reusedResponses } from "@/api"; +import { apiRoute, auth, jsonOrForm } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Attachment as AttachmentSchema, @@ -141,7 +141,8 @@ const route = createRoute({ }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/timelines/home.ts b/api/api/v1/timelines/home.ts index 05e7ba16..0819f1e6 100644 --- a/api/api/v1/timelines/home.ts +++ b/api/api/v1/timelines/home.ts @@ -1,10 +1,11 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Status as StatusSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Timeline } from "@versia/kit/db"; import { Notes } from "@versia/kit/tables"; import { and, eq, gt, gte, inArray, lt, or, sql } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -70,7 +71,7 @@ const route = createRoute({ }), }), }, - 422: reusedResponses[422], + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v1/timelines/public.ts b/api/api/v1/timelines/public.ts index 1c0961a6..59c2fc06 100644 --- a/api/api/v1/timelines/public.ts +++ b/api/api/v1/timelines/public.ts @@ -1,10 +1,11 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Status as StatusSchema, zBoolean } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Timeline } from "@versia/kit/db"; import { Notes } from "@versia/kit/tables"; import { and, eq, gt, gte, inArray, lt, or, sql } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "get", @@ -89,7 +90,7 @@ const route = createRoute({ }), }), }, - 422: reusedResponses[422], + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v2/filters/:id/index.ts b/api/api/v2/filters/:id/index.ts index 3e313b9b..147a3029 100644 --- a/api/api/v2/filters/:id/index.ts +++ b/api/api/v2/filters/:id/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, auth, jsonOrForm, reusedResponses } from "@/api"; +import { apiRoute, auth, jsonOrForm } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { FilterKeyword as FilterKeywordSchema, @@ -10,7 +10,6 @@ import { db } from "@versia/kit/db"; import { FilterKeywords, Filters } from "@versia/kit/tables"; import { type SQL, and, eq, inArray } from "drizzle-orm"; import { ApiError } from "~/classes/errors/api-error"; -import { ErrorSchema } from "~/types/api"; const routeGet = createRoute({ method: "get", @@ -44,11 +43,11 @@ const routeGet = createRoute({ description: "Filter not found", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, - 401: reusedResponses[401], + 401: ApiError.missingAuthentication().schema, }, }); @@ -125,11 +124,12 @@ const routePut = createRoute({ description: "Filter not found", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); @@ -161,11 +161,11 @@ const routeDelete = createRoute({ description: "Filter not found", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, - 401: reusedResponses[401], + 401: ApiError.missingAuthentication().schema, }, }); diff --git a/api/api/v2/filters/index.ts b/api/api/v2/filters/index.ts index e395f6d4..fac83d84 100644 --- a/api/api/v2/filters/index.ts +++ b/api/api/v2/filters/index.ts @@ -1,4 +1,4 @@ -import { apiRoute, auth, jsonOrForm, reusedResponses } from "@/api"; +import { apiRoute, auth, jsonOrForm } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { FilterKeyword as FilterKeywordSchema, @@ -8,6 +8,7 @@ import { RolePermission } from "@versia/client/schemas"; import { db } from "@versia/kit/db"; import { FilterKeywords, Filters } from "@versia/kit/tables"; import type { SQL } from "drizzle-orm"; +import { ApiError } from "~/classes/errors/api-error"; const routeGet = createRoute({ method: "get", @@ -34,7 +35,7 @@ const routeGet = createRoute({ }, }, }, - 401: reusedResponses[401], + 401: ApiError.missingAuthentication().schema, }, }); @@ -94,7 +95,8 @@ const routePost = createRoute({ }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v2/media/index.ts b/api/api/v2/media/index.ts index 610a705f..57ec4bac 100644 --- a/api/api/v2/media/index.ts +++ b/api/api/v2/media/index.ts @@ -1,9 +1,9 @@ -import { apiRoute, auth, reusedResponses } from "@/api"; +import { apiRoute, auth } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Attachment as AttachmentSchema } from "@versia/client/schemas"; import { RolePermission } from "@versia/client/schemas"; import { Media } from "@versia/kit/db"; -import { ErrorSchema } from "~/types/api"; +import { ApiError } from "~/classes/errors/api-error"; const route = createRoute({ method: "post", @@ -76,7 +76,7 @@ const route = createRoute({ description: "Payload too large", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, @@ -84,11 +84,12 @@ const route = createRoute({ description: "Unsupported media type", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, - ...reusedResponses, + 401: ApiError.missingAuthentication().schema, + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/api/v2/search/index.ts b/api/api/v2/search/index.ts index f0c12eee..0e7455cf 100644 --- a/api/api/v2/search/index.ts +++ b/api/api/v2/search/index.ts @@ -1,10 +1,4 @@ -import { - apiRoute, - auth, - parseUserAddress, - reusedResponses, - userAddressValidator, -} from "@/api"; +import { apiRoute, auth, parseUserAddress, userAddressValidator } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { Account as AccountSchema, @@ -19,7 +13,6 @@ import { and, eq, inArray, isNull, sql } from "drizzle-orm"; import { ApiError } from "~/classes/errors/api-error"; import { searchManager } from "~/classes/search/search-manager"; import { config } from "~/config.ts"; -import { ErrorSchema } from "~/types/api"; const route = createRoute({ method: "get", @@ -107,7 +100,7 @@ const route = createRoute({ "Cannot use resolve or offset without being authenticated", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, @@ -115,11 +108,11 @@ const route = createRoute({ description: "Search is not enabled on this server", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, - 422: reusedResponses[422], + 422: ApiError.validationFailed().schema, }, }); diff --git a/api/media/:hash/:name/index.ts b/api/media/:hash/:name/index.ts index 4b1a83c3..85e725a3 100644 --- a/api/media/:hash/:name/index.ts +++ b/api/media/:hash/:name/index.ts @@ -1,7 +1,6 @@ import { apiRoute } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import { ApiError } from "~/classes/errors/api-error"; -import { ErrorSchema } from "~/types/api"; const schemas = { param: z.object({ @@ -34,7 +33,7 @@ const route = createRoute({ description: "File not found", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, diff --git a/api/media/proxy/:id.ts b/api/media/proxy/:id.ts index 19ec3f31..9290baa6 100644 --- a/api/media/proxy/:id.ts +++ b/api/media/proxy/:id.ts @@ -4,7 +4,6 @@ import { proxy } from "hono/proxy"; import type { ContentfulStatusCode, StatusCode } from "hono/utils/http-status"; import { ApiError } from "~/classes/errors/api-error"; import { config } from "~/config.ts"; -import { ErrorSchema } from "~/types/api"; const schemas = { param: z.object({ @@ -34,7 +33,7 @@ const route = createRoute({ description: "Invalid URL to proxy", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, diff --git a/api/notes/:uuid/index.ts b/api/notes/:uuid/index.ts index 1e1c8e94..e1eb5973 100644 --- a/api/notes/:uuid/index.ts +++ b/api/notes/:uuid/index.ts @@ -7,7 +7,6 @@ import { Notes } from "@versia/kit/tables"; import { and, eq, inArray } from "drizzle-orm"; import { ApiError } from "~/classes/errors/api-error"; import { config } from "~/config.ts"; -import { ErrorSchema } from "~/types/api"; const route = createRoute({ method: "get", @@ -32,7 +31,7 @@ const route = createRoute({ "Entity not found, is remote, or the requester is not allowed to view it.", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, @@ -51,7 +50,7 @@ export default apiRoute((app) => ); if (!(note && (await note.isViewableByUser(null))) || note.isRemote()) { - throw new ApiError(404, "Note not found"); + throw ApiError.noteNotFound(); } // If base_url uses https and request uses http, rewrite request to use https diff --git a/api/notes/:uuid/quotes.ts b/api/notes/:uuid/quotes.ts index f1db3501..b281c879 100644 --- a/api/notes/:uuid/quotes.ts +++ b/api/notes/:uuid/quotes.ts @@ -8,7 +8,6 @@ import { Notes } from "@versia/kit/tables"; import { and, eq, inArray } from "drizzle-orm"; import { ApiError } from "~/classes/errors/api-error"; import { config } from "~/config.ts"; -import { ErrorSchema } from "~/types/api"; const route = createRoute({ method: "get", @@ -37,7 +36,7 @@ const route = createRoute({ "Entity not found, is remote, or the requester is not allowed to view it.", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, @@ -57,7 +56,7 @@ export default apiRoute((app) => ); if (!(note && (await note.isViewableByUser(null))) || note.isRemote()) { - throw new ApiError(404, "Note not found"); + throw ApiError.noteNotFound(); } const replies = await Note.manyFromSql( diff --git a/api/notes/:uuid/replies.ts b/api/notes/:uuid/replies.ts index aab2eec9..513c7c1f 100644 --- a/api/notes/:uuid/replies.ts +++ b/api/notes/:uuid/replies.ts @@ -8,7 +8,6 @@ import { Notes } from "@versia/kit/tables"; import { and, eq, inArray } from "drizzle-orm"; import { ApiError } from "~/classes/errors/api-error"; import { config } from "~/config.ts"; -import { ErrorSchema } from "~/types/api"; const route = createRoute({ method: "get", @@ -37,7 +36,7 @@ const route = createRoute({ "Entity not found, is remote, or the requester is not allowed to view it.", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, @@ -57,7 +56,7 @@ export default apiRoute((app) => ); if (!(note && (await note.isViewableByUser(null))) || note.isRemote()) { - throw new ApiError(404, "Note not found"); + throw ApiError.noteNotFound(); } const replies = await Note.manyFromSql( diff --git a/api/objects/:id/index.ts b/api/objects/:id/index.ts index 4c503d6f..b296910e 100644 --- a/api/objects/:id/index.ts +++ b/api/objects/:id/index.ts @@ -9,7 +9,7 @@ import { Likes, Notes } from "@versia/kit/tables"; import { and, eq, inArray, sql } from "drizzle-orm"; import { ApiError } from "~/classes/errors/api-error"; import { config } from "~/config.ts"; -import { ErrorSchema, type KnownEntity } from "~/types/api"; +import type { KnownEntity } from "~/types/api"; const route = createRoute({ method: "get", @@ -33,7 +33,7 @@ const route = createRoute({ description: "Object not found", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, @@ -41,7 +41,7 @@ const route = createRoute({ description: "Cannot view objects from remote instances", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, diff --git a/api/users/:uuid/inbox/index.ts b/api/users/:uuid/inbox/index.ts index 2976c025..39ab31e5 100644 --- a/api/users/:uuid/inbox/index.ts +++ b/api/users/:uuid/inbox/index.ts @@ -1,8 +1,8 @@ import { apiRoute } from "@/api"; import { createRoute, z } from "@hono/zod-openapi"; import type { Entity } from "@versia/federation/types"; +import { ApiError } from "~/classes/errors/api-error"; import { InboxJobType, inboxQueue } from "~/classes/queues/inbox"; -import { ErrorSchema } from "~/types/api"; const schemas = { param: z.object({ @@ -47,7 +47,7 @@ const route = createRoute({ description: "Bad request", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, @@ -55,7 +55,7 @@ const route = createRoute({ description: "Signature could not be verified", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, @@ -63,7 +63,7 @@ const route = createRoute({ description: "Cannot view users from remote instances", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, @@ -71,7 +71,7 @@ const route = createRoute({ description: "Not found", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, diff --git a/api/users/:uuid/index.ts b/api/users/:uuid/index.ts index 647cccd2..7c3845ff 100644 --- a/api/users/:uuid/index.ts +++ b/api/users/:uuid/index.ts @@ -3,7 +3,6 @@ import { createRoute, z } from "@hono/zod-openapi"; import { User as UserSchema } from "@versia/federation/schemas"; import { User } from "@versia/kit/db"; import { ApiError } from "~/classes/errors/api-error"; -import { ErrorSchema } from "~/types/api"; const schemas = { param: z.object({ @@ -31,19 +30,12 @@ const route = createRoute({ description: "Redirect to user profile (for web browsers). Uses user-agent for detection.", }, - 404: { - description: "User not found", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, - }, + 404: ApiError.accountNotFound().schema, 403: { description: "Cannot view users from remote instances", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, @@ -57,7 +49,7 @@ export default apiRoute((app) => const user = await User.fromId(uuid); if (!user) { - throw new ApiError(404, "User not found"); + throw ApiError.accountNotFound(); } if (user.isRemote()) { diff --git a/api/users/:uuid/outbox/index.ts b/api/users/:uuid/outbox/index.ts index dbaf626b..395dbb9c 100644 --- a/api/users/:uuid/outbox/index.ts +++ b/api/users/:uuid/outbox/index.ts @@ -9,7 +9,6 @@ import { Notes } from "@versia/kit/tables"; import { and, eq, inArray } from "drizzle-orm"; import { ApiError } from "~/classes/errors/api-error"; import { config } from "~/config.ts"; -import { ErrorSchema } from "~/types/api"; const schemas = { param: z.object({ @@ -43,7 +42,7 @@ const route = createRoute({ description: "User not found", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, @@ -51,7 +50,7 @@ const route = createRoute({ description: "Cannot view users from remote instances", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, diff --git a/api/well-known/webfinger/index.ts b/api/well-known/webfinger/index.ts index 8a2ce87f..b353d836 100644 --- a/api/well-known/webfinger/index.ts +++ b/api/well-known/webfinger/index.ts @@ -13,7 +13,6 @@ import { Users } from "@versia/kit/tables"; import { and, eq, isNull } from "drizzle-orm"; import { ApiError } from "~/classes/errors/api-error"; import { config } from "~/config.ts"; -import { ErrorSchema } from "~/types/api"; const schemas = { query: z.object({ @@ -46,14 +45,7 @@ const route = createRoute({ }, }, }, - 404: { - description: "User not found", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, - }, + 404: ApiError.accountNotFound().schema, }, }); @@ -85,7 +77,7 @@ export default apiRoute((app) => ); if (!user) { - throw new ApiError(404, "User not found"); + throw ApiError.accountNotFound(); } let activityPubUrl = ""; diff --git a/classes/errors/api-error.ts b/classes/errors/api-error.ts index e6a9ef52..772e2fb3 100644 --- a/classes/errors/api-error.ts +++ b/classes/errors/api-error.ts @@ -1,3 +1,6 @@ +// biome-ignore lint/correctness/noUndeclaredDependencies: Dependency of @hono/zod-openapi +import type { ResponseConfig } from "@asteasolutions/zod-to-openapi"; +import { z } from "@hono/zod-openapi"; import type { ContentfulStatusCode } from "hono/utils/http-status"; import type { JSONObject } from "hono/utils/types"; @@ -9,7 +12,7 @@ import type { JSONObject } from "hono/utils/types"; */ export class ApiError extends Error { /** - * @param {StatusCode} status - The status code of the error + * @param {ContentfulStatusCode} status - The status code of the error * @param {string} message - The message of the error * @param {string | JSONObject} [details] - The description of the error */ @@ -21,4 +24,135 @@ export class ApiError extends Error { super(message); this.name = "ApiError"; } + + public static zodSchema = z.object({ + error: z.string(), + details: z.string().or(z.record(z.string(), z.string())).optional(), + }); + + public get schema(): ResponseConfig { + return { + description: this.message, + content: { + "application/json": { + schema: ApiError.zodSchema, + }, + }, + }; + } + + public static missingAuthentication(): ApiError { + return new ApiError( + 401, + "Missing authentication", + "The Authorization header is missing or could not be parsed.", + ); + } + + public static forbidden(): ApiError { + return new ApiError( + 403, + "Missing permissions", + "You do not have permission to access or modify this resource.", + ); + } + + public static notFound(): ApiError { + return new ApiError( + 404, + "Not found", + "The requested resource could not be found.", + ); + } + + public static noteNotFound(): ApiError { + return new ApiError( + 404, + "Note not found", + "The requested note could not be found.", + ); + } + + public static accountNotFound(): ApiError { + return new ApiError( + 404, + "Account not found", + "The requested account could not be found.", + ); + } + + public static roleNotFound(): ApiError { + return new ApiError( + 404, + "Role not found", + "The requested role could not be found.", + ); + } + + public static instanceNotFound(): ApiError { + return new ApiError( + 404, + "Instance not found", + "The requested instance could not be found.", + ); + } + + public static pushSubscriptionNotFound(): ApiError { + return new ApiError( + 404, + "Push subscription not found", + "No push subscription associated with this access token", + ); + } + + public static tokenNotFound(): ApiError { + return new ApiError( + 404, + "Token not found", + "The requested token could not be found.", + ); + } + + public static mediaNotFound(): ApiError { + return new ApiError( + 404, + "Media not found", + "The requested media could not be found.", + ); + } + + public static applicationNotFound(): ApiError { + return new ApiError( + 404, + "Application not found", + "The requested application could not be found.", + ); + } + + public static emojiNotFound(): ApiError { + return new ApiError( + 404, + "Emoji not found", + "The requested emoji could not be found.", + ); + } + + public static notificationNotFound(): ApiError { + return new ApiError( + 404, + "Notification not found", + "The requested notification could not be found.", + ); + } + + public static validationFailed(): ApiError { + return new ApiError(422, "Invalid values in request"); + } + + public static internalServerError(): ApiError { + return new ApiError( + 500, + "Internal server error. This is likely a bug.", + ); + } } diff --git a/packages/client/schemas/notification.ts b/packages/client/schemas/notification.ts index 9104fae0..0ff40935 100644 --- a/packages/client/schemas/notification.ts +++ b/packages/client/schemas/notification.ts @@ -55,6 +55,7 @@ export const Notification = z event: z.undefined().openapi({ description: "Versia Server does not sever relationships, so this field is always empty.", + type: "null", }), moderation_warning: AccountWarning.optional().openapi({ description: diff --git a/plugins/openid/routes/sso/:id/index.ts b/plugins/openid/routes/sso/:id/index.ts index a136b668..0c8cd0b8 100644 --- a/plugins/openid/routes/sso/:id/index.ts +++ b/plugins/openid/routes/sso/:id/index.ts @@ -7,7 +7,6 @@ import { type SQL, eq } from "@versia/kit/drizzle"; import { OpenIdAccounts } from "@versia/kit/tables"; import { ApiError } from "~/classes/errors/api-error"; import type { PluginType } from "~/plugins/openid"; -import { ErrorSchema } from "~/types/api"; export default (plugin: PluginType): void => { plugin.registerRoute("/api/v1/sso", (app) => { @@ -41,14 +40,7 @@ export default (plugin: PluginType): void => { }, }, }, - 404: { - description: "Account not found", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, - }, + 404: ApiError.accountNotFound().schema, }, }), async (context) => { @@ -121,7 +113,7 @@ export default (plugin: PluginType): void => { description: "Account not found", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, diff --git a/plugins/openid/routes/sso/index.ts b/plugins/openid/routes/sso/index.ts index 5ed9ca83..7a982f9e 100644 --- a/plugins/openid/routes/sso/index.ts +++ b/plugins/openid/routes/sso/index.ts @@ -7,7 +7,7 @@ import { calculatePKCECodeChallenge, generateRandomCodeVerifier, } from "oauth4webapi"; -import { ErrorSchema } from "~/types/api"; +import { ApiError } from "~/classes/errors/api-error.ts"; import type { PluginType } from "../../index.ts"; import { oauthDiscoveryRequest, oauthRedirectUri } from "../../utils.ts"; @@ -91,7 +91,7 @@ export default (plugin: PluginType): void => { description: "Issuer not found", content: { "application/json": { - schema: ErrorSchema, + schema: ApiError.zodSchema, }, }, }, diff --git a/types/api.ts b/types/api.ts index 4e0bbee6..c528412d 100644 --- a/types/api.ts +++ b/types/api.ts @@ -1,5 +1,5 @@ import type { OpenAPIHono } from "@hono/zod-openapi"; -import { z } from "@hono/zod-openapi"; +import type { z } from "@hono/zod-openapi"; import type { Delete, Follow, @@ -18,10 +18,6 @@ import type { AuthData } from "~/classes/functions/user"; export type HttpVerb = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS"; -export const ErrorSchema = z.object({ - error: z.string(), -}); - export type HonoEnv = { Variables: { config: z.infer; diff --git a/utils/api.ts b/utils/api.ts index 52afee7f..29f97aee 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -30,43 +30,7 @@ import { fromZodError } from "zod-validation-error"; import { ApiError } from "~/classes/errors/api-error"; import type { AuthData } from "~/classes/functions/user"; import { config } from "~/config.ts"; -import { ErrorSchema, type HonoEnv } from "~/types/api"; - -export const reusedResponses = { - 401: { - description: "Invalid or missing Authorization header.", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, - }, - 422: { - description: "Invalid values in request", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, - }, -}; - -export const accountNotFound = { - description: "Account does not exist", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, -}; -export const noteNotFound = { - description: "Status does not exist", - content: { - "application/json": { - schema: ErrorSchema, - }, - }, -}; +import type { HonoEnv } from "~/types/api"; export const apiRoute = (fn: (app: OpenAPIHono) => void): typeof fn => fn; @@ -362,7 +326,7 @@ export const withNoteParam = every( const note = await Note.fromId(id, user?.id); if (!(note && (await note.isViewableByUser(user)))) { - throw new ApiError(404, "Note not found"); + throw ApiError.noteNotFound(); } context.set("note", note); @@ -435,7 +399,7 @@ export const withEmojiParam = every( const emoji = await Emoji.fromId(id); if (!emoji) { - throw new ApiError(404, "Emoji not found"); + throw ApiError.emojiNotFound(); } context.set("emoji", emoji);