From a601bc856e9d48eb3626e299f3083d616c97d6a7 Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Mon, 19 Aug 2024 21:03:59 +0200 Subject: [PATCH] refactor: :recycle: Use native Hono return functions instead of custom ones --- app.ts | 12 +- middlewares/agent-bans.ts | 3 +- middlewares/boundary-check.ts | 7 +- middlewares/ip-bans.ts | 7 +- middlewares/url-check.ts | 7 +- server/api/api/auth/login/index.ts | 4 +- server/api/api/v1/accounts/:id/block.ts | 7 +- server/api/api/v1/accounts/:id/follow.ts | 7 +- server/api/api/v1/accounts/:id/followers.ts | 5 +- server/api/api/v1/accounts/:id/following.ts | 5 +- server/api/api/v1/accounts/:id/index.ts | 5 +- server/api/api/v1/accounts/:id/mute.ts | 7 +- server/api/api/v1/accounts/:id/note.ts | 7 +- server/api/api/v1/accounts/:id/pin.ts | 7 +- server/api/api/v1/accounts/:id/refetch.ts | 7 +- .../v1/accounts/:id/remove_from_followers.ts | 7 +- server/api/api/v1/accounts/:id/statuses.ts | 5 +- server/api/api/v1/accounts/:id/unblock.ts | 7 +- server/api/api/v1/accounts/:id/unfollow.ts | 9 +- server/api/api/v1/accounts/:id/unmute.ts | 7 +- server/api/api/v1/accounts/:id/unpin.ts | 7 +- .../v1/accounts/familiar_followers/index.ts | 9 +- server/api/api/v1/accounts/id/index.ts | 5 +- server/api/api/v1/accounts/index.ts | 6 +- server/api/api/v1/accounts/lookup/index.ts | 13 +-- .../api/v1/accounts/relationships/index.ts | 5 +- server/api/api/v1/accounts/search/index.ts | 5 +- .../v1/accounts/update_credentials/index.ts | 12 +- .../v1/accounts/verify_credentials/index.ts | 5 +- server/api/api/v1/apps/index.ts | 3 +- .../api/v1/apps/verify_credentials/index.ts | 9 +- server/api/api/v1/blocks/index.ts | 5 +- server/api/api/v1/challenges/index.ts | 10 +- server/api/api/v1/custom_emojis/index.test.ts | 12 +- server/api/api/v1/custom_emojis/index.ts | 3 +- server/api/api/v1/emojis/:id/index.ts | 32 +++--- server/api/api/v1/emojis/index.ts | 23 ++-- server/api/api/v1/favourites/index.ts | 5 +- .../follow_requests/:account_id/authorize.ts | 7 +- .../v1/follow_requests/:account_id/reject.ts | 7 +- server/api/api/v1/follow_requests/index.ts | 5 +- server/api/api/v1/frontend/config/index.ts | 5 +- .../api/v1/instance/extended_description.ts | 5 +- server/api/api/v1/instance/index.ts | 6 +- server/api/api/v1/instance/privacy_policy.ts | 5 +- server/api/api/v1/instance/rules.ts | 5 +- server/api/api/v1/instance/tos.ts | 5 +- server/api/api/v1/markers/index.ts | 9 +- server/api/api/v1/media/:id/index.ts | 17 +-- server/api/api/v1/media/index.ts | 11 +- server/api/api/v1/mutes/index.ts | 5 +- .../api/api/v1/notifications/:id/dismiss.ts | 5 +- server/api/api/v1/notifications/:id/index.ts | 7 +- .../api/api/v1/notifications/clear/index.ts | 5 +- .../notifications/destroy_multiple/index.ts | 5 +- server/api/api/v1/notifications/index.test.ts | 8 +- server/api/api/v1/notifications/index.ts | 11 +- server/api/api/v1/profile/avatar.ts | 5 +- server/api/api/v1/profile/header.ts | 5 +- server/api/api/v1/roles/:id/index.ts | 32 ++---- server/api/api/v1/roles/index.ts | 5 +- server/api/api/v1/sso/:id/index.ts | 20 ++-- server/api/api/v1/sso/index.ts | 20 ++-- server/api/api/v1/statuses/:id/context.ts | 5 +- server/api/api/v1/statuses/:id/favourite.ts | 9 +- .../api/v1/statuses/:id/favourited_by.test.ts | 4 +- .../api/api/v1/statuses/:id/favourited_by.ts | 7 +- server/api/api/v1/statuses/:id/index.ts | 20 ++-- server/api/api/v1/statuses/:id/pin.ts | 11 +- server/api/api/v1/statuses/:id/reblog.ts | 13 +-- .../api/v1/statuses/:id/reblogged_by.test.ts | 4 +- .../api/api/v1/statuses/:id/reblogged_by.ts | 7 +- server/api/api/v1/statuses/:id/source.ts | 7 +- server/api/api/v1/statuses/:id/unfavourite.ts | 9 +- server/api/api/v1/statuses/:id/unpin.ts | 11 +- server/api/api/v1/statuses/:id/unreblog.ts | 11 +- server/api/api/v1/statuses/index.test.ts | 30 +++-- server/api/api/v1/statuses/index.ts | 17 ++- server/api/api/v1/timelines/home.test.ts | 14 ++- server/api/api/v1/timelines/home.ts | 5 +- server/api/api/v1/timelines/public.test.ts | 24 ++-- server/api/api/v1/timelines/public.ts | 3 +- server/api/api/v2/filters/:id/index.ts | 16 +-- server/api/api/v2/filters/index.ts | 22 ++-- server/api/api/v2/instance/index.ts | 6 +- server/api/api/v2/media/index.ts | 13 ++- server/api/api/v2/search/index.ts | 19 ++-- server/api/media/:hash/:name/index.ts | 4 +- server/api/media/proxy/:id.ts | 6 +- .../api/oauth/sso/:issuer/callback/index.ts | 4 +- server/api/oauth/token/index.ts | 65 ++++++----- server/api/objects/:id/index.ts | 10 +- server/api/users/:uuid/inbox/index.ts | 89 +++++++++++---- server/api/users/:uuid/index.ts | 8 +- server/api/users/:uuid/outbox/index.ts | 9 +- server/api/well-known/jwks/index.ts | 5 +- server/api/well-known/nodeinfo/2.0/index.ts | 5 +- .../well-known/openid-configuration/index.ts | 5 +- server/api/well-known/versia.ts | 5 +- server/api/well-known/webfinger/index.ts | 13 ++- tests/api/accounts.test.ts | 28 ++--- tests/api/statuses.test.ts | 18 +-- tests/oauth.test.ts | 12 +- utils/api.ts | 106 ++++++++++-------- utils/response.ts | 20 ---- 105 files changed, 639 insertions(+), 581 deletions(-) diff --git a/app.ts b/app.ts index 705562cf..d7975f8b 100644 --- a/app.ts +++ b/app.ts @@ -1,4 +1,4 @@ -import { errorResponse, jsonResponse, response } from "@/response"; +import { response } from "@/response"; import { sentry } from "@/sentry"; import { Hono } from "@hono/hono"; import { getLogger } from "@logtape/logtape"; @@ -76,8 +76,10 @@ export const appFactory = async () => { proxy?.headers.set("Cache-Control", "max-age=31536000"); if (!proxy || proxy.status === 404) { - return errorResponse( - "Route not found on proxy or API route. Are you using the correct HTTP method?", + return context.json( + { + error: "Route not found on proxy or API route. Are you using the correct HTTP method?", + }, 404, ); } @@ -95,11 +97,11 @@ export const appFactory = async () => { return proxy; }); - app.onError((error) => { + app.onError((error, c) => { const serverLogger = getLogger("server"); serverLogger.error`${error}`; sentry?.captureException(error); - return jsonResponse( + return c.json( { error: "A server error occured", name: error.name, diff --git a/middlewares/agent-bans.ts b/middlewares/agent-bans.ts index 7bec2451..f82a4180 100644 --- a/middlewares/agent-bans.ts +++ b/middlewares/agent-bans.ts @@ -1,4 +1,3 @@ -import { errorResponse } from "@/response"; import { createMiddleware } from "@hono/hono/factory"; import { config } from "~/packages/config-manager"; @@ -8,7 +7,7 @@ export const agentBans = createMiddleware(async (context, next) => { for (const agent of config.http.banned_user_agents) { if (new RegExp(agent).test(ua)) { - return errorResponse("Forbidden", 403); + return context.json({ error: "Forbidden" }, 403); } } diff --git a/middlewares/boundary-check.ts b/middlewares/boundary-check.ts index bbed965f..63fbf59c 100644 --- a/middlewares/boundary-check.ts +++ b/middlewares/boundary-check.ts @@ -1,4 +1,3 @@ -import { errorResponse } from "@/response"; import { createMiddleware } from "@hono/hono/factory"; export const boundaryCheck = createMiddleware(async (context, next) => { @@ -7,8 +6,10 @@ export const boundaryCheck = createMiddleware(async (context, next) => { if (contentType?.includes("multipart/form-data")) { if (!contentType.includes("boundary")) { - return errorResponse( - "You are sending a request with a multipart/form-data content type but without a boundary. Please include a boundary in the Content-Type header. For more information, visit https://stackoverflow.com/questions/3508338/what-is-the-boundary-in-multipart-form-data", + return context.json( + { + error: "You are sending a request with a multipart/form-data content type but without a boundary. Please include a boundary in the Content-Type header. For more information, visit https://stackoverflow.com/questions/3508338/what-is-the-boundary-in-multipart-form-data", + }, 400, ); } diff --git a/middlewares/ip-bans.ts b/middlewares/ip-bans.ts index 35d7d0e6..8ee07fd1 100644 --- a/middlewares/ip-bans.ts +++ b/middlewares/ip-bans.ts @@ -1,4 +1,3 @@ -import { errorResponse } from "@/response"; import { sentry } from "@/sentry"; import { createMiddleware } from "@hono/hono/factory"; import { getLogger } from "@logtape/logtape"; @@ -19,7 +18,7 @@ export const ipBans = createMiddleware(async (context, next) => { for (const ip of config.http.banned_ips) { try { if (matches(ip, requestIp?.address)) { - return errorResponse("Forbidden", 403); + return context.json({ error: "Forbidden" }, 403); } } catch (e) { const logger = getLogger("server"); @@ -28,8 +27,8 @@ export const ipBans = createMiddleware(async (context, next) => { logger.error`${e}`; sentry?.captureException(e); - return errorResponse( - `A server error occured: ${(e as Error).message}`, + return context.json( + { error: `A server error occured: ${(e as Error).message}` }, 500, ); } diff --git a/middlewares/url-check.ts b/middlewares/url-check.ts index 6cbb77ca..06a55e72 100644 --- a/middlewares/url-check.ts +++ b/middlewares/url-check.ts @@ -1,4 +1,3 @@ -import { errorResponse } from "@/response"; import { createMiddleware } from "@hono/hono/factory"; import { config } from "~/packages/config-manager"; @@ -7,8 +6,10 @@ export const urlCheck = createMiddleware(async (context, next) => { const baseUrl = new URL(config.http.base_url); if (new URL(context.req.url).origin !== baseUrl.origin) { - return errorResponse( - `Request URL ${context.req.url} does not match base URL ${baseUrl.origin}`, + return context.json( + { + error: `Request URL ${context.req.url} does not match base URL ${baseUrl.origin}`, + }, 400, ); } diff --git a/server/api/api/auth/login/index.ts b/server/api/api/auth/login/index.ts index 7c61c2dc..29ef11fe 100644 --- a/server/api/api/auth/login/index.ts +++ b/server/api/api/auth/login/index.ts @@ -1,5 +1,5 @@ import { apiRoute, applyConfig, handleZodError } from "@/api"; -import { errorResponse, redirect } from "@/response"; +import { redirect } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { eq, or } from "drizzle-orm"; import { SignJWT } from "jose"; @@ -159,7 +159,7 @@ export default apiRoute((app) => }); if (!application) { - return errorResponse("Invalid application", 400); + return context.json({ error: "Invalid application" }, 400); } const searchParams = new URLSearchParams({ diff --git a/server/api/api/v1/accounts/:id/block.ts b/server/api/api/v1/accounts/:id/block.ts index ace8235d..0efcd9fb 100644 --- a/server/api/api/v1/accounts/:id/block.ts +++ b/server/api/api/v1/accounts/:id/block.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; @@ -42,13 +41,13 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const otherUser = await User.fromId(id); if (!otherUser) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } const foundRelationship = await Relationship.fromOwnerAndSubject( @@ -62,7 +61,7 @@ export default apiRoute((app) => }); } - return jsonResponse(foundRelationship.toApi()); + return context.json(foundRelationship.toApi()); }, ), ); diff --git a/server/api/api/v1/accounts/:id/follow.ts b/server/api/api/v1/accounts/:id/follow.ts index 82fb7f9a..5e28e99e 100644 --- a/server/api/api/v1/accounts/:id/follow.ts +++ b/server/api/api/v1/accounts/:id/follow.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import ISO6391 from "iso-639-1"; import { z } from "zod"; @@ -55,13 +54,13 @@ export default apiRoute((app) => const { reblogs, notify, languages } = context.req.valid("json"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const otherUser = await User.fromId(id); if (!otherUser) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } let relationship = await Relationship.fromOwnerAndSubject( @@ -77,7 +76,7 @@ export default apiRoute((app) => }); } - return jsonResponse(relationship.toApi()); + return context.json(relationship.toApi()); }, ), ); diff --git a/server/api/api/v1/accounts/:id/followers.ts b/server/api/api/v1/accounts/:id/followers.ts index 0d79cc05..d880c7fd 100644 --- a/server/api/api/v1/accounts/:id/followers.ts +++ b/server/api/api/v1/accounts/:id/followers.ts @@ -5,7 +5,6 @@ import { handleZodError, idValidator, } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import { z } from "zod"; @@ -61,7 +60,7 @@ export default apiRoute((app) => // TODO: Add follower/following privacy settings if (!otherUser) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } const { objects, link } = await Timeline.getUserTimeline( @@ -75,7 +74,7 @@ export default apiRoute((app) => context.req.url, ); - return jsonResponse( + return context.json( await Promise.all(objects.map((object) => object.toApi())), 200, { diff --git a/server/api/api/v1/accounts/:id/following.ts b/server/api/api/v1/accounts/:id/following.ts index f46ab0b4..5bb7ee7c 100644 --- a/server/api/api/v1/accounts/:id/following.ts +++ b/server/api/api/v1/accounts/:id/following.ts @@ -5,7 +5,6 @@ import { handleZodError, idValidator, } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import { z } from "zod"; @@ -58,7 +57,7 @@ export default apiRoute((app) => const otherUser = await User.fromId(id); if (!otherUser) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } // TODO: Add follower/following privacy settings @@ -74,7 +73,7 @@ export default apiRoute((app) => context.req.url, ); - return jsonResponse( + return context.json( await Promise.all(objects.map((object) => object.toApi())), 200, { diff --git a/server/api/api/v1/accounts/:id/index.ts b/server/api/api/v1/accounts/:id/index.ts index 29e482fb..e217c35f 100644 --- a/server/api/api/v1/accounts/:id/index.ts +++ b/server/api/api/v1/accounts/:id/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; @@ -40,10 +39,10 @@ export default apiRoute((app) => const foundUser = await User.fromId(id); if (!foundUser) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } - return jsonResponse(foundUser.toApi(user?.id === foundUser.id)); + return context.json(foundUser.toApi(user?.id === foundUser.id)); }, ), ); diff --git a/server/api/api/v1/accounts/:id/mute.ts b/server/api/api/v1/accounts/:id/mute.ts index 63c48254..c2b96f9f 100644 --- a/server/api/api/v1/accounts/:id/mute.ts +++ b/server/api/api/v1/accounts/:id/mute.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; @@ -54,13 +53,13 @@ export default apiRoute((app) => const { notifications } = context.req.valid("json"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const otherUser = await User.fromId(id); if (!otherUser) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } const foundRelationship = await Relationship.fromOwnerAndSubject( @@ -74,7 +73,7 @@ export default apiRoute((app) => mutingNotifications: notifications ?? true, }); - return jsonResponse(foundRelationship.toApi()); + return context.json(foundRelationship.toApi()); }, ), ); diff --git a/server/api/api/v1/accounts/:id/note.ts b/server/api/api/v1/accounts/:id/note.ts index e6b2b8aa..fb1d0060 100644 --- a/server/api/api/v1/accounts/:id/note.ts +++ b/server/api/api/v1/accounts/:id/note.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; @@ -47,13 +46,13 @@ export default apiRoute((app) => const { comment } = context.req.valid("json"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const otherUser = await User.fromId(id); if (!otherUser) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } const foundRelationship = await Relationship.fromOwnerAndSubject( @@ -65,7 +64,7 @@ export default apiRoute((app) => note: comment, }); - return jsonResponse(foundRelationship.toApi()); + return context.json(foundRelationship.toApi()); }, ), ); diff --git a/server/api/api/v1/accounts/:id/pin.ts b/server/api/api/v1/accounts/:id/pin.ts index 51f7faa4..5a785dea 100644 --- a/server/api/api/v1/accounts/:id/pin.ts +++ b/server/api/api/v1/accounts/:id/pin.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; @@ -42,13 +41,13 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const otherUser = await User.fromId(id); if (!otherUser) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } const foundRelationship = await Relationship.fromOwnerAndSubject( @@ -60,7 +59,7 @@ export default apiRoute((app) => endorsed: true, }); - return jsonResponse(foundRelationship.toApi()); + return context.json(foundRelationship.toApi()); }, ), ); diff --git a/server/api/api/v1/accounts/:id/refetch.ts b/server/api/api/v1/accounts/:id/refetch.ts index 68e09f7d..6647e56a 100644 --- a/server/api/api/v1/accounts/:id/refetch.ts +++ b/server/api/api/v1/accounts/:id/refetch.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; @@ -38,18 +37,18 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const otherUser = await User.fromId(id); if (!otherUser) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } const newUser = await otherUser.updateFromRemote(); - return jsonResponse(newUser.toApi(false)); + return context.json(newUser.toApi(false)); }, ), ); diff --git a/server/api/api/v1/accounts/:id/remove_from_followers.ts b/server/api/api/v1/accounts/:id/remove_from_followers.ts index be85dd4d..a5b24728 100644 --- a/server/api/api/v1/accounts/:id/remove_from_followers.ts +++ b/server/api/api/v1/accounts/:id/remove_from_followers.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; @@ -42,13 +41,13 @@ export default apiRoute((app) => const { user: self } = context.req.valid("header"); if (!self) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const otherUser = await User.fromId(id); if (!otherUser) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } const oppositeRelationship = await Relationship.fromOwnerAndSubject( @@ -67,7 +66,7 @@ export default apiRoute((app) => otherUser, ); - return jsonResponse(foundRelationship.toApi()); + return context.json(foundRelationship.toApi()); }, ), ); diff --git a/server/api/api/v1/accounts/:id/statuses.ts b/server/api/api/v1/accounts/:id/statuses.ts index 74dda97a..e7e9f490 100644 --- a/server/api/api/v1/accounts/:id/statuses.ts +++ b/server/api/api/v1/accounts/:id/statuses.ts @@ -5,7 +5,6 @@ import { handleZodError, idValidator, } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, eq, gt, gte, isNull, lt, sql } from "drizzle-orm"; import { z } from "zod"; @@ -75,7 +74,7 @@ export default apiRoute((app) => const otherUser = await User.fromId(id); if (!otherUser) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } const { @@ -109,7 +108,7 @@ export default apiRoute((app) => user?.id, ); - return jsonResponse( + return context.json( await Promise.all(objects.map((note) => note.toApi(otherUser))), 200, { diff --git a/server/api/api/v1/accounts/:id/unblock.ts b/server/api/api/v1/accounts/:id/unblock.ts index 2024ad70..c60bed77 100644 --- a/server/api/api/v1/accounts/:id/unblock.ts +++ b/server/api/api/v1/accounts/:id/unblock.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; @@ -42,13 +41,13 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const otherUser = await User.fromId(id); if (!otherUser) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } const foundRelationship = await Relationship.fromOwnerAndSubject( @@ -62,7 +61,7 @@ export default apiRoute((app) => }); } - return jsonResponse(foundRelationship.toApi()); + return context.json(foundRelationship.toApi()); }, ), ); diff --git a/server/api/api/v1/accounts/:id/unfollow.ts b/server/api/api/v1/accounts/:id/unfollow.ts index 58cc4cb1..2184fd6a 100644 --- a/server/api/api/v1/accounts/:id/unfollow.ts +++ b/server/api/api/v1/accounts/:id/unfollow.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; @@ -42,13 +41,13 @@ export default apiRoute((app) => const { user: self } = context.req.valid("header"); if (!self) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const otherUser = await User.fromId(id); if (!otherUser) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } const foundRelationship = await Relationship.fromOwnerAndSubject( @@ -57,10 +56,10 @@ export default apiRoute((app) => ); if (!(await self.unfollow(otherUser, foundRelationship))) { - return errorResponse("Failed to unfollow user", 500); + return context.json({ error: "Failed to unfollow user" }, 500); } - return jsonResponse(foundRelationship.toApi()); + return context.json(foundRelationship.toApi()); }, ), ); diff --git a/server/api/api/v1/accounts/:id/unmute.ts b/server/api/api/v1/accounts/:id/unmute.ts index 8e4693b0..fcc50908 100644 --- a/server/api/api/v1/accounts/:id/unmute.ts +++ b/server/api/api/v1/accounts/:id/unmute.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; @@ -42,13 +41,13 @@ export default apiRoute((app) => const { user: self } = context.req.valid("header"); if (!self) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const user = await User.fromId(id); if (!user) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } const foundRelationship = await Relationship.fromOwnerAndSubject( @@ -63,7 +62,7 @@ export default apiRoute((app) => }); } - return jsonResponse(foundRelationship.toApi()); + return context.json(foundRelationship.toApi()); }, ), ); diff --git a/server/api/api/v1/accounts/:id/unpin.ts b/server/api/api/v1/accounts/:id/unpin.ts index d2082652..86beaf6a 100644 --- a/server/api/api/v1/accounts/:id/unpin.ts +++ b/server/api/api/v1/accounts/:id/unpin.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; @@ -42,13 +41,13 @@ export default apiRoute((app) => const { user: self } = context.req.valid("header"); if (!self) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const otherUser = await User.fromId(id); if (!otherUser) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } const foundRelationship = await Relationship.fromOwnerAndSubject( @@ -62,7 +61,7 @@ export default apiRoute((app) => }); } - return jsonResponse(foundRelationship.toApi()); + return context.json(foundRelationship.toApi()); }, ), ); diff --git a/server/api/api/v1/accounts/familiar_followers/index.ts b/server/api/api/v1/accounts/familiar_followers/index.ts index b6a555be..44c08700 100644 --- a/server/api/api/v1/accounts/familiar_followers/index.ts +++ b/server/api/api/v1/accounts/familiar_followers/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError, qsQuery } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { inArray } from "drizzle-orm"; import { z } from "zod"; @@ -41,7 +40,7 @@ export default apiRoute((app) => const { id: ids } = context.req.valid("query"); if (!self) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const idFollowerRelationships = @@ -60,7 +59,7 @@ export default apiRoute((app) => }); if (idFollowerRelationships.length === 0) { - return jsonResponse([]); + return context.json([]); } // Find users that you follow in idFollowerRelationships @@ -82,7 +81,7 @@ export default apiRoute((app) => ); if (relevantRelationships.length === 0) { - return jsonResponse([]); + return context.json([]); } const finalUsers = await User.manyFromSql( @@ -92,7 +91,7 @@ export default apiRoute((app) => ), ); - return jsonResponse(finalUsers.map((o) => o.toApi())); + return context.json(finalUsers.map((o) => o.toApi())); }, ), ); diff --git a/server/api/api/v1/accounts/id/index.ts b/server/api/api/v1/accounts/id/index.ts index b13078de..8f470875 100644 --- a/server/api/api/v1/accounts/id/index.ts +++ b/server/api/api/v1/accounts/id/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, eq, isNull } from "drizzle-orm"; import { z } from "zod"; @@ -42,10 +41,10 @@ export default apiRoute((app) => ); if (!user) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } - return jsonResponse(user.toApi()); + return context.json(user.toApi()); }, ), ); diff --git a/server/api/api/v1/accounts/index.ts b/server/api/api/v1/accounts/index.ts index 5d432cd5..920eae75 100644 --- a/server/api/api/v1/accounts/index.ts +++ b/server/api/api/v1/accounts/index.ts @@ -1,5 +1,5 @@ import { apiRoute, applyConfig, auth, handleZodError, jsonOrForm } from "@/api"; -import { jsonResponse, response } from "@/response"; +import { response } from "@/response"; import { tempmailDomains } from "@/tempmail"; import { zValidator } from "@hono/zod-validator"; import { and, eq, isNull } from "drizzle-orm"; @@ -52,7 +52,7 @@ export default apiRoute((app) => context.req.valid("json"); if (!config.signups.registration) { - return jsonResponse( + return context.json( { error: "Registration is disabled", }, @@ -235,7 +235,7 @@ export default apiRoute((app) => .join(", ")}`, ) .join(", "); - return jsonResponse( + return context.json( { error: `Validation failed: ${errorsText}`, details: Object.fromEntries( diff --git a/server/api/api/v1/accounts/lookup/index.ts b/server/api/api/v1/accounts/lookup/index.ts index 2ffb06aa..464d22e8 100644 --- a/server/api/api/v1/accounts/lookup/index.ts +++ b/server/api/api/v1/accounts/lookup/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; import { @@ -50,7 +49,7 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!acct) { - return errorResponse("Invalid acct parameter", 400); + return context.json({ error: "Invalid acct parameter" }, 400); } // Check if acct is matching format username@domain.com or @username@domain.com @@ -86,10 +85,10 @@ export default apiRoute((app) => const foundAccount = await User.resolve(uri); if (foundAccount) { - return jsonResponse(foundAccount.toApi()); + return context.json(foundAccount.toApi()); } - return errorResponse("Account not found", 404); + return context.json({ error: "Account not found" }, 404); } let username = acct; @@ -100,11 +99,11 @@ export default apiRoute((app) => const account = await User.fromSql(eq(Users.username, username)); if (account) { - return jsonResponse(account.toApi()); + return context.json(account.toApi()); } - return errorResponse( - `Account with username ${username} not found`, + return context.json( + { error: `Account with username ${username} not found` }, 404, ); }, diff --git a/server/api/api/v1/accounts/relationships/index.ts b/server/api/api/v1/accounts/relationships/index.ts index b52a367c..d33c4d3f 100644 --- a/server/api/api/v1/accounts/relationships/index.ts +++ b/server/api/api/v1/accounts/relationships/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError, qsQuery } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; @@ -41,7 +40,7 @@ export default apiRoute((app) => const ids = Array.isArray(id) ? id : [id]; if (!self) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const relationships = await Relationship.fromOwnerAndSubjects( @@ -55,7 +54,7 @@ export default apiRoute((app) => ids.indexOf(b.data.subjectId), ); - return jsonResponse(relationships.map((r) => r.toApi())); + return context.json(relationships.map((r) => r.toApi())); }, ), ); diff --git a/server/api/api/v1/accounts/search/index.ts b/server/api/api/v1/accounts/search/index.ts index aaa4fc74..6bb54fb9 100644 --- a/server/api/api/v1/accounts/search/index.ts +++ b/server/api/api/v1/accounts/search/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { eq, ilike, not, or, sql } from "drizzle-orm"; import { @@ -80,7 +79,7 @@ export default apiRoute((app) => const { user: self } = context.req.valid("header"); if (!self && following) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const [username, host] = q.replace(/^@/, "").split("@"); @@ -126,7 +125,7 @@ export default apiRoute((app) => const result = indexOfCorrectSort.map((index) => accounts[index]); - return jsonResponse(result.map((acct) => acct.toApi())); + return context.json(result.map((acct) => acct.toApi())); }, ), ); diff --git a/server/api/api/v1/accounts/update_credentials/index.ts b/server/api/api/v1/accounts/update_credentials/index.ts index 4f6f204c..c386a80b 100644 --- a/server/api/api/v1/accounts/update_credentials/index.ts +++ b/server/api/api/v1/accounts/update_credentials/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError, jsonOrForm } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { sanitizedHtmlStrip } from "@/sanitization"; import { zValidator } from "@hono/zod-validator"; import { and, eq, isNull } from "drizzle-orm"; @@ -149,7 +148,7 @@ export default apiRoute((app) => } = context.req.valid("json"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const self = user.data; @@ -192,7 +191,10 @@ export default apiRoute((app) => ); if (existingUser) { - return errorResponse("Username is already taken", 400); + return context.json( + { error: "Username is already taken" }, + 400, + ); } self.username = username; @@ -330,10 +332,10 @@ export default apiRoute((app) => const output = await User.fromId(self.id); if (!output) { - return errorResponse("Couldn't edit user", 500); + return context.json({ error: "Couldn't edit user" }, 500); } - return jsonResponse(output.toApi()); + return context.json(output.toApi()); }, ), ); diff --git a/server/api/api/v1/accounts/verify_credentials/index.ts b/server/api/api/v1/accounts/verify_credentials/index.ts index ed34726e..e0275a25 100644 --- a/server/api/api/v1/accounts/verify_credentials/index.ts +++ b/server/api/api/v1/accounts/verify_credentials/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; export const meta = applyConfig({ allowedMethods: ["GET"], @@ -24,10 +23,10 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } - return jsonResponse(user.toApi(true)); + return context.json(user.toApi(true)); }, ), ); diff --git a/server/api/api/v1/apps/index.ts b/server/api/api/v1/apps/index.ts index 754c755b..769366be 100644 --- a/server/api/api/v1/apps/index.ts +++ b/server/api/api/v1/apps/index.ts @@ -1,6 +1,5 @@ import { apiRoute, applyConfig, handleZodError, jsonOrForm } from "@/api"; import { randomString } from "@/math"; -import { jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { db } from "~/drizzle/db"; @@ -67,7 +66,7 @@ export default apiRoute((app) => .returning() )[0]; - return jsonResponse({ + return context.json({ id: app.id, name: app.name, website: app.website, diff --git a/server/api/api/v1/apps/verify_credentials/index.ts b/server/api/api/v1/apps/verify_credentials/index.ts index 3f74c017..bdcdf0a1 100644 --- a/server/api/api/v1/apps/verify_credentials/index.ts +++ b/server/api/api/v1/apps/verify_credentials/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { getFromToken } from "~/classes/functions/application"; import { RolePermissions } from "~/drizzle/schema"; @@ -27,19 +26,19 @@ export default apiRoute((app) => const { user, token } = context.req.valid("header"); if (!token) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const application = await getFromToken(token); if (!application) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } - return jsonResponse({ + return context.json({ name: application.name, website: application.website, vapid_key: application.vapidKey, diff --git a/server/api/api/v1/blocks/index.ts b/server/api/api/v1/blocks/index.ts index 632eba0f..7965caeb 100644 --- a/server/api/api/v1/blocks/index.ts +++ b/server/api/api/v1/blocks/index.ts @@ -5,7 +5,6 @@ import { handleZodError, idValidator, } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import { z } from "zod"; @@ -50,7 +49,7 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const { objects: blocks, link } = await Timeline.getUserTimeline( @@ -64,7 +63,7 @@ export default apiRoute((app) => context.req.url, ); - return jsonResponse( + return context.json( blocks.map((u) => u.toApi()), 200, { diff --git a/server/api/api/v1/challenges/index.ts b/server/api/api/v1/challenges/index.ts index e4fd9014..b7f8f1b4 100644 --- a/server/api/api/v1/challenges/index.ts +++ b/server/api/api/v1/challenges/index.ts @@ -1,6 +1,5 @@ import { apiRoute, applyConfig, auth } from "@/api"; import { generateChallenge } from "@/challenges"; -import { errorResponse, jsonResponse } from "@/response"; import { config } from "~/packages/config-manager"; export const meta = applyConfig({ @@ -23,14 +22,17 @@ export default apiRoute((app) => meta.allowedMethods, meta.route, auth(meta.auth, meta.permissions), - async (_context) => { + async (context) => { if (!config.validation.challenges.enabled) { - return errorResponse("Challenges are disabled in config", 400); + return context.json( + { error: "Challenges are disabled in config" }, + 400, + ); } const result = await generateChallenge(); - return jsonResponse({ + return context.json({ id: result.id, ...result.challenge, }); diff --git a/server/api/api/v1/custom_emojis/index.test.ts b/server/api/api/v1/custom_emojis/index.test.ts index 53a0ab86..9ead7b64 100644 --- a/server/api/api/v1/custom_emojis/index.test.ts +++ b/server/api/api/v1/custom_emojis/index.test.ts @@ -78,7 +78,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const emojis = await response.json(); @@ -110,7 +112,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const emojis = await response.json(); @@ -138,7 +142,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const emojis = await response.json(); diff --git a/server/api/api/v1/custom_emojis/index.ts b/server/api/api/v1/custom_emojis/index.ts index 8f7832fe..ee1a8c60 100644 --- a/server/api/api/v1/custom_emojis/index.ts +++ b/server/api/api/v1/custom_emojis/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth } from "@/api"; -import { jsonResponse } from "@/response"; import { and, eq, isNull, or } from "drizzle-orm"; import { Emojis, RolePermissions } from "~/drizzle/schema"; import { Emoji } from "~/packages/database-interface/emoji"; @@ -37,7 +36,7 @@ export default apiRoute((app) => ), ); - return jsonResponse(emojis.map((emoji) => emoji.toApi())); + return context.json(emojis.map((emoji) => emoji.toApi())); }, ), ); diff --git a/server/api/api/v1/emojis/:id/index.ts b/server/api/api/v1/emojis/:id/index.ts index a2e8ad88..ab5e2e99 100644 --- a/server/api/api/v1/emojis/:id/index.ts +++ b/server/api/api/v1/emojis/:id/index.ts @@ -7,7 +7,7 @@ import { jsonOrForm, } from "@/api"; import { mimeLookup } from "@/content_types"; -import { errorResponse, jsonResponse, response } from "@/response"; +import { response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; import { z } from "zod"; @@ -80,13 +80,13 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const emoji = await Emoji.fromId(id); if (!emoji) { - return errorResponse("Emoji not found", 404); + return context.json({ error: "Emoji not found" }, 404); } // Check if user is admin @@ -94,7 +94,7 @@ export default apiRoute((app) => !user.hasPermission(RolePermissions.ManageEmojis) && emoji.data.ownerId !== user.data.id ) { - return jsonResponse( + return context.json( { error: `You cannot modify this emoji, as it is either global, not owned by you, or you do not have the '${RolePermissions.ManageEmojis}' permission to manage global emojis`, }, @@ -117,8 +117,10 @@ export default apiRoute((app) => const form = context.req.valid("json"); if (!form) { - return errorResponse( - "Invalid form data (must supply at least one of: shortcode, element, alt, category)", + return context.json( + { + error: "Invalid form data (must supply at least one of: shortcode, element, alt, category)", + }, 422, ); } @@ -132,8 +134,10 @@ export default apiRoute((app) => ) && form.global === undefined ) { - return errorResponse( - "Invalid form data (must supply shortcode and/or element and/or alt and/or global)", + return context.json( + { + error: "Invalid form data (must supply at least one of: shortcode, element, alt, category)", + }, 422, ); } @@ -142,8 +146,10 @@ export default apiRoute((app) => !user.hasPermission(RolePermissions.ManageEmojis) && form.global ) { - return errorResponse( - `Only users with the '${RolePermissions.ManageEmojis}' permission can make an emoji global or not`, + return context.json( + { + error: `Only users with the '${RolePermissions.ManageEmojis}' permission can make an emoji global or not`, + }, 401, ); } @@ -158,7 +164,7 @@ export default apiRoute((app) => : await mimeLookup(form.element); if (!contentType.startsWith("image/")) { - return jsonResponse( + return context.json( { error: `Emojis must be images (png, jpg, gif, etc.). Detected: ${contentType}`, }, @@ -190,11 +196,11 @@ export default apiRoute((app) => await emoji.update(modified); - return jsonResponse(emoji.toApi()); + return context.json(emoji.toApi()); } case "GET": { - return jsonResponse(emoji.toApi()); + return context.json(emoji.toApi()); } } }, diff --git a/server/api/api/v1/emojis/index.ts b/server/api/api/v1/emojis/index.ts index 26838217..a3b6efe3 100644 --- a/server/api/api/v1/emojis/index.ts +++ b/server/api/api/v1/emojis/index.ts @@ -7,7 +7,6 @@ import { jsonOrForm, } from "@/api"; import { mimeLookup } from "@/content_types"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, eq, isNull, or } from "drizzle-orm"; import { z } from "zod"; @@ -73,12 +72,14 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } if (!user.hasPermission(RolePermissions.ManageEmojis) && global) { - return errorResponse( - `Only users with the '${RolePermissions.ManageEmojis}' permission can upload global emojis`, + return context.json( + { + error: `Only users with the '${RolePermissions.ManageEmojis}' permission can upload global emojis`, + }, 401, ); } @@ -93,8 +94,10 @@ export default apiRoute((app) => ); if (existing) { - return errorResponse( - `An emoji with the shortcode ${shortcode} already exists, either owned by you or global.`, + return context.json( + { + error: `An emoji with the shortcode ${shortcode} already exists, either owned by you or global.`, + }, 422, ); } @@ -108,8 +111,10 @@ export default apiRoute((app) => : await mimeLookup(element); if (!contentType.startsWith("image/")) { - return errorResponse( - `Emojis must be images (png, jpg, gif, etc.). Detected: ${contentType}`, + return context.json( + { + error: `Emojis must be images (png, jpg, gif, etc.). Detected: ${contentType}`, + }, 422, ); } @@ -135,7 +140,7 @@ export default apiRoute((app) => alt, }); - return jsonResponse(emoji.toApi()); + return context.json(emoji.toApi()); }, ), ); diff --git a/server/api/api/v1/favourites/index.ts b/server/api/api/v1/favourites/index.ts index 66d09e92..2142ce37 100644 --- a/server/api/api/v1/favourites/index.ts +++ b/server/api/api/v1/favourites/index.ts @@ -5,7 +5,6 @@ import { handleZodError, idValidator, } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import { z } from "zod"; @@ -49,7 +48,7 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const { objects: favourites, link } = @@ -65,7 +64,7 @@ export default apiRoute((app) => user?.id, ); - return jsonResponse( + return context.json( await Promise.all( favourites.map(async (note) => note.toApi(user)), ), diff --git a/server/api/api/v1/follow_requests/:account_id/authorize.ts b/server/api/api/v1/follow_requests/:account_id/authorize.ts index 8046ee00..b34230dd 100644 --- a/server/api/api/v1/follow_requests/:account_id/authorize.ts +++ b/server/api/api/v1/follow_requests/:account_id/authorize.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { sendFollowAccept } from "~/classes/functions/user"; @@ -38,7 +37,7 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const { account_id } = context.req.valid("param"); @@ -46,7 +45,7 @@ export default apiRoute((app) => const account = await User.fromId(account_id); if (!account) { - return errorResponse("Account not found", 404); + return context.json({ error: "Account not found" }, 404); } const oppositeRelationship = await Relationship.fromOwnerAndSubject( @@ -70,7 +69,7 @@ export default apiRoute((app) => await sendFollowAccept(account, user); } - return jsonResponse(foundRelationship.toApi()); + return context.json(foundRelationship.toApi()); }, ), ); diff --git a/server/api/api/v1/follow_requests/:account_id/reject.ts b/server/api/api/v1/follow_requests/:account_id/reject.ts index 29d56a7f..cf484230 100644 --- a/server/api/api/v1/follow_requests/:account_id/reject.ts +++ b/server/api/api/v1/follow_requests/:account_id/reject.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { sendFollowReject } from "~/classes/functions/user"; @@ -38,7 +37,7 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const { account_id } = context.req.valid("param"); @@ -46,7 +45,7 @@ export default apiRoute((app) => const account = await User.fromId(account_id); if (!account) { - return errorResponse("Account not found", 404); + return context.json({ error: "Account not found" }, 404); } const oppositeRelationship = await Relationship.fromOwnerAndSubject( @@ -70,7 +69,7 @@ export default apiRoute((app) => await sendFollowReject(account, user); } - return jsonResponse(foundRelationship.toApi()); + return context.json(foundRelationship.toApi()); }, ), ); diff --git a/server/api/api/v1/follow_requests/index.ts b/server/api/api/v1/follow_requests/index.ts index 15a48c91..d474d6ed 100644 --- a/server/api/api/v1/follow_requests/index.ts +++ b/server/api/api/v1/follow_requests/index.ts @@ -5,7 +5,6 @@ import { handleZodError, idValidator, } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import { z } from "zod"; @@ -49,7 +48,7 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const { objects: followRequests, link } = @@ -64,7 +63,7 @@ export default apiRoute((app) => context.req.url, ); - return jsonResponse( + return context.json( followRequests.map((u) => u.toApi()), 200, { diff --git a/server/api/api/v1/frontend/config/index.ts b/server/api/api/v1/frontend/config/index.ts index 5473f1c5..f5383c27 100644 --- a/server/api/api/v1/frontend/config/index.ts +++ b/server/api/api/v1/frontend/config/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig } from "@/api"; -import { jsonResponse } from "@/response"; import { config } from "~/packages/config-manager"; export const meta = applyConfig({ @@ -15,7 +14,7 @@ export const meta = applyConfig({ }); export default apiRoute((app) => - app.on(meta.allowedMethods, meta.route, () => { - return jsonResponse(config.frontend.settings); + app.on(meta.allowedMethods, meta.route, (context) => { + return context.json(config.frontend.settings); }), ); diff --git a/server/api/api/v1/instance/extended_description.ts b/server/api/api/v1/instance/extended_description.ts index e6acae82..03e4b513 100644 --- a/server/api/api/v1/instance/extended_description.ts +++ b/server/api/api/v1/instance/extended_description.ts @@ -1,6 +1,5 @@ import { apiRoute, applyConfig, auth } from "@/api"; import { renderMarkdownInPath } from "@/markdown"; -import { jsonResponse } from "@/response"; import { config } from "~/packages/config-manager"; export const meta = applyConfig({ @@ -20,13 +19,13 @@ export default apiRoute((app) => meta.allowedMethods, meta.route, auth(meta.auth, meta.permissions), - async () => { + async (context) => { const { content, lastModified } = await renderMarkdownInPath( config.instance.extended_description_path ?? "", "This is a [Versia](https://versia.pub) server with the default extended description.", ); - return jsonResponse({ + return context.json({ updated_at: lastModified.toISOString(), content, }); diff --git a/server/api/api/v1/instance/index.ts b/server/api/api/v1/instance/index.ts index c1a158e7..399c13b7 100644 --- a/server/api/api/v1/instance/index.ts +++ b/server/api/api/v1/instance/index.ts @@ -1,5 +1,5 @@ import { apiRoute, applyConfig, auth } from "@/api"; -import { jsonResponse, proxyUrl } from "@/response"; +import { proxyUrl } from "@/response"; import { and, eq, isNull } from "drizzle-orm"; import { Users } from "~/drizzle/schema"; import manifest from "~/package.json"; @@ -25,7 +25,7 @@ export default apiRoute((app) => meta.allowedMethods, meta.route, auth(meta.auth, meta.permissions), - async () => { + async (context) => { // Get software version from package.json const version = manifest.version; @@ -40,7 +40,7 @@ export default apiRoute((app) => const knownDomainsCount = await Instance.getCount(); // TODO: fill in more values - return jsonResponse({ + return context.json({ approval_required: false, configuration: { polls: { diff --git a/server/api/api/v1/instance/privacy_policy.ts b/server/api/api/v1/instance/privacy_policy.ts index 9dcedee0..e9a5902e 100644 --- a/server/api/api/v1/instance/privacy_policy.ts +++ b/server/api/api/v1/instance/privacy_policy.ts @@ -1,6 +1,5 @@ import { apiRoute, applyConfig, auth } from "@/api"; import { renderMarkdownInPath } from "@/markdown"; -import { jsonResponse } from "@/response"; import { config } from "~/packages/config-manager"; export const meta = applyConfig({ @@ -20,13 +19,13 @@ export default apiRoute((app) => meta.allowedMethods, meta.route, auth(meta.auth, meta.permissions), - async () => { + async (context) => { const { content, lastModified } = await renderMarkdownInPath( config.instance.privacy_policy_path ?? "", "This instance has not provided any privacy policy.", ); - return jsonResponse({ + return context.json({ updated_at: lastModified.toISOString(), content, }); diff --git a/server/api/api/v1/instance/rules.ts b/server/api/api/v1/instance/rules.ts index 2df3cae9..4fa56a6f 100644 --- a/server/api/api/v1/instance/rules.ts +++ b/server/api/api/v1/instance/rules.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth } from "@/api"; -import { jsonResponse } from "@/response"; import { config } from "~/packages/config-manager"; export const meta = applyConfig({ @@ -19,8 +18,8 @@ export default apiRoute((app) => meta.allowedMethods, meta.route, auth(meta.auth, meta.permissions), - async () => { - return jsonResponse( + async (context) => { + return context.json( config.signups.rules.map((rule, index) => ({ id: String(index), text: rule, diff --git a/server/api/api/v1/instance/tos.ts b/server/api/api/v1/instance/tos.ts index 04bc79eb..6d53f1fa 100644 --- a/server/api/api/v1/instance/tos.ts +++ b/server/api/api/v1/instance/tos.ts @@ -1,6 +1,5 @@ import { apiRoute, applyConfig, auth } from "@/api"; import { renderMarkdownInPath } from "@/markdown"; -import { jsonResponse } from "@/response"; import { config } from "~/packages/config-manager"; export const meta = applyConfig({ @@ -20,13 +19,13 @@ export default apiRoute((app) => meta.allowedMethods, meta.route, auth(meta.auth, meta.permissions), - async () => { + async (context) => { const { content, lastModified } = await renderMarkdownInPath( config.instance.tos_path ?? "", "This instance has not provided any terms of service.", ); - return jsonResponse({ + return context.json({ updated_at: lastModified.toISOString(), content, }); diff --git a/server/api/api/v1/markers/index.ts b/server/api/api/v1/markers/index.ts index d8aaea27..c0085d52 100644 --- a/server/api/api/v1/markers/index.ts +++ b/server/api/api/v1/markers/index.ts @@ -5,7 +5,6 @@ import { handleZodError, idValidator, } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import type { Marker as ApiMarker } from "@lysand-org/client/types"; import { and, count, eq } from "drizzle-orm"; @@ -54,13 +53,13 @@ export default apiRoute((app) => const timeline = Array.isArray(timelines) ? timelines : []; if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } switch (context.req.method) { case "GET": { if (!timeline) { - return jsonResponse({}); + return context.json({}); } const markers: ApiMarker = { @@ -132,7 +131,7 @@ export default apiRoute((app) => } } - return jsonResponse(markers); + return context.json(markers); } case "POST": { @@ -212,7 +211,7 @@ export default apiRoute((app) => }; } - return jsonResponse(markers); + return context.json(markers); } } }, diff --git a/server/api/api/v1/media/:id/index.ts b/server/api/api/v1/media/:id/index.ts index b3d7fbd3..eed060c2 100644 --- a/server/api/api/v1/media/:id/index.ts +++ b/server/api/api/v1/media/:id/index.ts @@ -5,7 +5,7 @@ import { handleZodError, idValidator, } from "@/api"; -import { errorResponse, jsonResponse, response } from "@/response"; +import { response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { MediaManager } from "~/classes/media/media-manager"; @@ -54,19 +54,22 @@ export default apiRoute((app) => const { id } = context.req.valid("param"); if (!id.match(idValidator)) { - return errorResponse("Invalid ID, must be of type UUIDv7", 404); + return context.json( + { error: "Invalid ID, must be of type UUIDv7" }, + 404, + ); } const attachment = await Attachment.fromId(id); if (!attachment) { - return errorResponse("Media not found", 404); + return context.json({ error: "Media not found" }, 404); } switch (context.req.method) { case "GET": { if (attachment.data.url) { - return jsonResponse(attachment.toApi()); + return context.json(attachment.toApi()); } return response(null, 206); } @@ -95,14 +98,14 @@ export default apiRoute((app) => thumbnailUrl, }); - return jsonResponse(attachment.toApi()); + return context.json(attachment.toApi()); } - return jsonResponse(attachment.toApi()); + return context.json(attachment.toApi()); } } - return errorResponse("Method not allowed", 405); + return context.json({ error: "Method not allowed" }, 405); }, ), ); diff --git a/server/api/api/v1/media/index.ts b/server/api/api/v1/media/index.ts index 2d81a449..0053ea47 100644 --- a/server/api/api/v1/media/index.ts +++ b/server/api/api/v1/media/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import sharp from "sharp"; import { z } from "zod"; @@ -46,8 +45,10 @@ export default apiRoute((app) => const { file, thumbnail, description } = context.req.valid("form"); if (file.size > config.validation.max_media_size) { - return errorResponse( - `File too large, max size is ${config.validation.max_media_size} bytes`, + return context.json( + { + error: `File too large, max size is ${config.validation.max_media_size} bytes`, + }, 413, ); } @@ -56,7 +57,7 @@ export default apiRoute((app) => config.validation.enforce_mime_types && !config.validation.allowed_mime_types.includes(file.type) ) { - return errorResponse("Invalid file type", 415); + return context.json({ error: "Invalid file type" }, 415); } const sha256 = new Bun.SHA256(); @@ -95,7 +96,7 @@ export default apiRoute((app) => // TODO: Add job to process videos and other media - return jsonResponse(newAttachment.toApi()); + return context.json(newAttachment.toApi()); }, ), ); diff --git a/server/api/api/v1/mutes/index.ts b/server/api/api/v1/mutes/index.ts index 2a63780c..0428e2ca 100644 --- a/server/api/api/v1/mutes/index.ts +++ b/server/api/api/v1/mutes/index.ts @@ -5,7 +5,6 @@ import { handleZodError, idValidator, } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import { z } from "zod"; @@ -49,7 +48,7 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const { objects: mutes, link } = await Timeline.getUserTimeline( @@ -63,7 +62,7 @@ export default apiRoute((app) => context.req.url, ); - return jsonResponse( + return context.json( mutes.map((u) => u.toApi()), 200, { diff --git a/server/api/api/v1/notifications/:id/dismiss.ts b/server/api/api/v1/notifications/:id/dismiss.ts index ab1bdc60..f422c6a3 100644 --- a/server/api/api/v1/notifications/:id/dismiss.ts +++ b/server/api/api/v1/notifications/:id/dismiss.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; import { z } from "zod"; @@ -39,7 +38,7 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } await db @@ -49,7 +48,7 @@ export default apiRoute((app) => }) .where(eq(Notifications.id, id)); - return jsonResponse({}); + return context.json({}); }, ), ); diff --git a/server/api/api/v1/notifications/:id/index.ts b/server/api/api/v1/notifications/:id/index.ts index 84cc5a1a..59d60c7d 100644 --- a/server/api/api/v1/notifications/:id/index.ts +++ b/server/api/api/v1/notifications/:id/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { findManyNotifications } from "~/classes/functions/notification"; @@ -38,7 +37,7 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const notification = ( @@ -53,10 +52,10 @@ export default apiRoute((app) => )[0]; if (!notification) { - return errorResponse("Notification not found", 404); + return context.json({ error: "Notification not found" }, 404); } - return jsonResponse(notification); + return context.json(notification); }, ), ); diff --git a/server/api/api/v1/notifications/clear/index.ts b/server/api/api/v1/notifications/clear/index.ts index a32fb26f..8ea0b672 100644 --- a/server/api/api/v1/notifications/clear/index.ts +++ b/server/api/api/v1/notifications/clear/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { eq } from "drizzle-orm"; import { db } from "~/drizzle/db"; import { Notifications, RolePermissions } from "~/drizzle/schema"; @@ -28,7 +27,7 @@ export default apiRoute((app) => async (context) => { const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } await db @@ -38,7 +37,7 @@ export default apiRoute((app) => }) .where(eq(Notifications.notifiedId, user.id)); - return jsonResponse({}); + return context.json({}); }, ), ); diff --git a/server/api/api/v1/notifications/destroy_multiple/index.ts b/server/api/api/v1/notifications/destroy_multiple/index.ts index fb8e2cc0..50b666b1 100644 --- a/server/api/api/v1/notifications/destroy_multiple/index.ts +++ b/server/api/api/v1/notifications/destroy_multiple/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, eq, inArray } from "drizzle-orm"; import { z } from "zod"; @@ -38,7 +37,7 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const { "ids[]": ids } = context.req.valid("query"); @@ -55,7 +54,7 @@ export default apiRoute((app) => ), ); - return jsonResponse({}); + return context.json({}); }, ), ); diff --git a/server/api/api/v1/notifications/index.test.ts b/server/api/api/v1/notifications/index.test.ts index 864a7a35..8176e11e 100644 --- a/server/api/api/v1/notifications/index.test.ts +++ b/server/api/api/v1/notifications/index.test.ts @@ -111,7 +111,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const objects = (await response.json()) as ApiNotification[]; @@ -163,7 +165,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const objects = (await response.json()) as ApiNotification[]; diff --git a/server/api/api/v1/notifications/index.ts b/server/api/api/v1/notifications/index.ts index 31012d03..d645bd4f 100644 --- a/server/api/api/v1/notifications/index.ts +++ b/server/api/api/v1/notifications/index.ts @@ -5,7 +5,6 @@ import { handleZodError, idValidator, } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { fetchTimeline } from "@/timelines"; import { zValidator } from "@hono/zod-validator"; import { sql } from "drizzle-orm"; @@ -105,7 +104,7 @@ export default apiRoute((app) => async (context) => { const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const { @@ -119,8 +118,10 @@ export default apiRoute((app) => } = context.req.valid("query"); if (types && exclude_types) { - return errorResponse( - "Can't use both types and exclude_types", + return context.json( + { + error: "Can't use both types and exclude_types", + }, 400, ); } @@ -191,7 +192,7 @@ export default apiRoute((app) => user.id, ); - return jsonResponse( + return context.json( await Promise.all(objects.map((n) => notificationToApi(n))), 200, { diff --git a/server/api/api/v1/profile/avatar.ts b/server/api/api/v1/profile/avatar.ts index 68fbf9b2..6a4079c1 100644 --- a/server/api/api/v1/profile/avatar.ts +++ b/server/api/api/v1/profile/avatar.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ @@ -26,14 +25,14 @@ export default apiRoute((app) => const { user: self } = context.req.valid("header"); if (!self) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } await self.update({ avatar: "", }); - return jsonResponse(self.toApi(true)); + return context.json(self.toApi(true)); }, ), ); diff --git a/server/api/api/v1/profile/header.ts b/server/api/api/v1/profile/header.ts index 8fced22d..ee51a91f 100644 --- a/server/api/api/v1/profile/header.ts +++ b/server/api/api/v1/profile/header.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { RolePermissions } from "~/drizzle/schema"; export const meta = applyConfig({ @@ -26,14 +25,14 @@ export default apiRoute((app) => const { user: self } = context.req.valid("header"); if (!self) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } await self.update({ header: "", }); - return jsonResponse(self.toApi(true)); + return context.json(self.toApi(true)); }, ), ); diff --git a/server/api/api/v1/roles/:id/index.ts b/server/api/api/v1/roles/:id/index.ts index d406908e..c09206ea 100644 --- a/server/api/api/v1/roles/:id/index.ts +++ b/server/api/api/v1/roles/:id/index.ts @@ -1,5 +1,5 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse, response } from "@/response"; +import { response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; @@ -40,7 +40,7 @@ export default apiRoute((app) => const { id } = context.req.valid("param"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const userRoles = await Role.getUserRoles( @@ -50,12 +50,12 @@ export default apiRoute((app) => const role = await Role.fromId(id); if (!role) { - return errorResponse("Role not found", 404); + return context.json({ error: "Role not found" }, 404); } switch (context.req.method) { case "GET": { - return jsonResponse(role.toApi()); + return context.json(role.toApi()); } case "POST": { @@ -66,14 +66,10 @@ export default apiRoute((app) => ); if (role.data.priority > userHighestRole.data.priority) { - return errorResponse( - `Cannot assign role '${ - role.data.name - }' with priority ${ - role.data.priority - } to user with highest role priority ${ - userHighestRole.data.priority - }`, + return context.json( + { + error: `Cannot assign role '${role.data.name}' with priority ${role.data.priority} to user with highest role priority ${userHighestRole.data.priority}`, + }, 403, ); } @@ -90,14 +86,10 @@ export default apiRoute((app) => ); if (role.data.priority > userHighestRole.data.priority) { - return errorResponse( - `Cannot remove role '${ - role.data.name - }' with priority ${ - role.data.priority - } from user with highest role priority ${ - userHighestRole.data.priority - }`, + return context.json( + { + error: `Cannot remove role '${role.data.name}' with priority ${role.data.priority} from user with highest role priority ${userHighestRole.data.priority}`, + }, 403, ); } diff --git a/server/api/api/v1/roles/index.ts b/server/api/api/v1/roles/index.ts index 9edb6b7b..9ac88a73 100644 --- a/server/api/api/v1/roles/index.ts +++ b/server/api/api/v1/roles/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { Role } from "~/packages/database-interface/role"; export const meta = applyConfig({ @@ -23,7 +22,7 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const userRoles = await Role.getUserRoles( @@ -31,7 +30,7 @@ export default apiRoute((app) => user.data.isAdmin, ); - return jsonResponse(userRoles.map((r) => r.toApi())); + return context.json(userRoles.map((r) => r.toApi())); }, ), ); diff --git a/server/api/api/v1/sso/:id/index.ts b/server/api/api/v1/sso/:id/index.ts index 2ab95a10..51b9d031 100644 --- a/server/api/api/v1/sso/:id/index.ts +++ b/server/api/api/v1/sso/:id/index.ts @@ -1,5 +1,5 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse, proxyUrl, response } from "@/response"; +import { proxyUrl, response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; import { z } from "zod"; @@ -44,7 +44,7 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const issuer = config.oidc.providers.find( @@ -52,7 +52,7 @@ export default apiRoute((app) => ); if (!issuer) { - return errorResponse("Issuer not found", 404); + return context.json({ error: "Issuer not found" }, 404); } switch (context.req.method) { @@ -67,13 +67,15 @@ export default apiRoute((app) => }); if (!account) { - return errorResponse( - "Account not found or is not linked to this issuer", + return context.json( + { + error: "Account not found or is not linked to this issuer", + }, 404, ); } - return jsonResponse({ + return context.json({ id: issuer.id, name: issuer.name, icon: proxyUrl(issuer.icon) || undefined, @@ -89,8 +91,10 @@ export default apiRoute((app) => }); if (!account) { - return errorResponse( - "Account not found or is not linked to this issuer", + return context.json( + { + error: "Account not found or is not linked to this issuer", + }, 404, ); } diff --git a/server/api/api/v1/sso/index.ts b/server/api/api/v1/sso/index.ts index e50db72f..aad3e5f9 100644 --- a/server/api/api/v1/sso/index.ts +++ b/server/api/api/v1/sso/index.ts @@ -1,7 +1,7 @@ import { apiRoute, applyConfig, auth, handleZodError, jsonOrForm } from "@/api"; import { oauthRedirectUri } from "@/constants"; import { randomString } from "@/math"; -import { errorResponse, jsonResponse, proxyUrl } from "@/response"; +import { proxyUrl } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { calculatePKCECodeChallenge, @@ -58,7 +58,7 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } switch (context.req.method) { @@ -68,7 +68,7 @@ export default apiRoute((app) => where: (User, { eq }) => eq(User.userId, user.id), }); - return jsonResponse( + return context.json( accounts .map((account) => { const issuer = config.oidc.providers.find( @@ -95,8 +95,8 @@ export default apiRoute((app) => } case "POST": { if (!form) { - return errorResponse( - "Missing issuer in form body", + return context.json( + { error: "Missing issuer in form body" }, 400, ); } @@ -104,8 +104,8 @@ export default apiRoute((app) => const { issuer: issuerId } = form; if (!issuerId) { - return errorResponse( - "Missing issuer in form body", + return context.json( + { error: "Missing issuer in form body" }, 400, ); } @@ -115,8 +115,8 @@ export default apiRoute((app) => ); if (!issuer) { - return errorResponse( - `Issuer ${issuerId} not found`, + return context.json( + { error: `Issuer ${issuerId} not found` }, 404, ); } @@ -157,7 +157,7 @@ export default apiRoute((app) => const codeChallenge = await calculatePKCECodeChallenge(codeVerifier); - return jsonResponse({ + return context.json({ link: `${ authServer.authorization_endpoint }?${new URLSearchParams({ diff --git a/server/api/api/v1/statuses/:id/context.ts b/server/api/api/v1/statuses/:id/context.ts index df02dee0..f4ac9ca8 100644 --- a/server/api/api/v1/statuses/:id/context.ts +++ b/server/api/api/v1/statuses/:id/context.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; @@ -40,14 +39,14 @@ export default apiRoute((app) => const foundStatus = await Note.fromId(id, user?.id); if (!foundStatus) { - return errorResponse("Record not found", 404); + return context.json({ error: "Record not found" }, 404); } const ancestors = await foundStatus.getAncestors(user ?? null); const descendants = await foundStatus.getDescendants(user ?? null); - return jsonResponse({ + return context.json({ ancestors: await Promise.all( ancestors.map((status) => status.toApi(user)), ), diff --git a/server/api/api/v1/statuses/:id/favourite.ts b/server/api/api/v1/statuses/:id/favourite.ts index 010b55ff..66b75678 100644 --- a/server/api/api/v1/statuses/:id/favourite.ts +++ b/server/api/api/v1/statuses/:id/favourite.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { createLike } from "~/classes/functions/like"; @@ -40,13 +39,13 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const note = await Note.fromId(id, user?.id); if (!note?.isViewableByUser(user)) { - return errorResponse("Record not found", 404); + return context.json({ error: "Record not found" }, 404); } const existingLike = await db.query.Likes.findFirst({ @@ -64,10 +63,10 @@ export default apiRoute((app) => const newNote = await Note.fromId(id, user.id); if (!newNote) { - return errorResponse("Record not found", 404); + return context.json({ error: "Record not found" }, 404); } - return jsonResponse(await newNote.toApi(user)); + return context.json(await newNote.toApi(user)); }, ), ); diff --git a/server/api/api/v1/statuses/:id/favourited_by.test.ts b/server/api/api/v1/statuses/:id/favourited_by.test.ts index acf28144..78637f92 100644 --- a/server/api/api/v1/statuses/:id/favourited_by.test.ts +++ b/server/api/api/v1/statuses/:id/favourited_by.test.ts @@ -61,7 +61,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const objects = (await response.json()) as ApiAccount[]; diff --git a/server/api/api/v1/statuses/:id/favourited_by.ts b/server/api/api/v1/statuses/:id/favourited_by.ts index f216bd76..b53dd0ba 100644 --- a/server/api/api/v1/statuses/:id/favourited_by.ts +++ b/server/api/api/v1/statuses/:id/favourited_by.ts @@ -5,7 +5,6 @@ import { handleZodError, idValidator, } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import { z } from "zod"; @@ -55,13 +54,13 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const status = await Note.fromId(id, user?.id); if (!status?.isViewableByUser(user)) { - return errorResponse("Record not found", 404); + return context.json({ error: "Record not found" }, 404); } const { objects, link } = await Timeline.getUserTimeline( @@ -75,7 +74,7 @@ export default apiRoute((app) => context.req.url, ); - return jsonResponse( + return context.json( objects.map((user) => user.toApi()), 200, { diff --git a/server/api/api/v1/statuses/:id/index.ts b/server/api/api/v1/statuses/:id/index.ts index f91c49c3..5cbc1288 100644 --- a/server/api/api/v1/statuses/:id/index.ts +++ b/server/api/api/v1/statuses/:id/index.ts @@ -6,7 +6,6 @@ import { idValidator, jsonOrForm, } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import ISO6391 from "iso-639-1"; import { z } from "zod"; @@ -121,16 +120,16 @@ export default apiRoute((app) => const note = await Note.fromId(id, user?.id); if (!note?.isViewableByUser(user)) { - return errorResponse("Record not found", 404); + return context.json({ error: "Record not found" }, 404); } switch (context.req.method) { case "GET": { - return jsonResponse(await note.toApi(user)); + return context.json(await note.toApi(user)); } case "DELETE": { if (note.author.id !== user?.id) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } // TODO: Delete and redraft @@ -141,15 +140,15 @@ export default apiRoute((app) => undoFederationRequest(user, note.getUri()), ); - return jsonResponse(await note.toApi(user), 200); + return context.json(await note.toApi(user), 200); } case "PUT": { if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } if (note.author.id !== user.id) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } if (media_ids.length > 0) { @@ -157,7 +156,10 @@ export default apiRoute((app) => await Attachment.fromIds(media_ids); if (foundAttachments.length !== media_ids.length) { - return errorResponse("Invalid media IDs", 422); + return context.json( + { error: "Invalid media IDs" }, + 422, + ); } } @@ -175,7 +177,7 @@ export default apiRoute((app) => mediaAttachments: media_ids, }); - return jsonResponse(await newNote.toApi(user)); + return context.json(await newNote.toApi(user)); } } }, diff --git a/server/api/api/v1/statuses/:id/pin.ts b/server/api/api/v1/statuses/:id/pin.ts index f79416fd..dc817875 100644 --- a/server/api/api/v1/statuses/:id/pin.ts +++ b/server/api/api/v1/statuses/:id/pin.ts @@ -5,7 +5,6 @@ import { handleZodError, idValidator, } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { db } from "~/drizzle/db"; @@ -44,17 +43,17 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const foundStatus = await Note.fromId(id, user?.id); if (!foundStatus) { - return errorResponse("Record not found", 404); + return context.json({ error: "Record not found" }, 404); } if (foundStatus.author.id !== user.id) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } if ( @@ -66,12 +65,12 @@ export default apiRoute((app) => ), }) ) { - return errorResponse("Already pinned", 422); + return context.json({ error: "Already pinned" }, 422); } await user.pin(foundStatus); - return jsonResponse(await foundStatus.toApi(user)); + return context.json(await foundStatus.toApi(user)); }, ), ); diff --git a/server/api/api/v1/statuses/:id/reblog.ts b/server/api/api/v1/statuses/:id/reblog.ts index 9bd20b94..5cfd6027 100644 --- a/server/api/api/v1/statuses/:id/reblog.ts +++ b/server/api/api/v1/statuses/:id/reblog.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError, jsonOrForm } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, eq } from "drizzle-orm"; import { z } from "zod"; @@ -45,13 +44,13 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const foundStatus = await Note.fromId(id, user.id); if (!foundStatus?.isViewableByUser(user)) { - return errorResponse("Record not found", 404); + return context.json({ error: "Record not found" }, 404); } const existingReblog = await Note.fromSql( @@ -62,7 +61,7 @@ export default apiRoute((app) => ); if (existingReblog) { - return errorResponse("Already reblogged", 422); + return context.json({ error: "Already reblogged" }, 422); } const newReblog = await Note.insert({ @@ -75,13 +74,13 @@ export default apiRoute((app) => }); if (!newReblog) { - return errorResponse("Failed to reblog", 500); + return context.json({ error: "Failed to reblog" }, 500); } const finalNewReblog = await Note.fromId(newReblog.id, user?.id); if (!finalNewReblog) { - return errorResponse("Failed to reblog", 500); + return context.json({ error: "Failed to reblog" }, 500); } if (foundStatus.author.isLocal() && user.isLocal()) { @@ -93,7 +92,7 @@ export default apiRoute((app) => }); } - return jsonResponse(await finalNewReblog.toApi(user)); + return context.json(await finalNewReblog.toApi(user)); }, ), ); diff --git a/server/api/api/v1/statuses/:id/reblogged_by.test.ts b/server/api/api/v1/statuses/:id/reblogged_by.test.ts index 2a9be319..e8a32bc5 100644 --- a/server/api/api/v1/statuses/:id/reblogged_by.test.ts +++ b/server/api/api/v1/statuses/:id/reblogged_by.test.ts @@ -61,7 +61,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const objects = (await response.json()) as ApiAccount[]; diff --git a/server/api/api/v1/statuses/:id/reblogged_by.ts b/server/api/api/v1/statuses/:id/reblogged_by.ts index a2137700..a837c5a2 100644 --- a/server/api/api/v1/statuses/:id/reblogged_by.ts +++ b/server/api/api/v1/statuses/:id/reblogged_by.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import { z } from "zod"; @@ -48,13 +47,13 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const status = await Note.fromId(id, user.id); if (!status?.isViewableByUser(user)) { - return errorResponse("Record not found", 404); + return context.json({ error: "Record not found" }, 404); } const { objects, link } = await Timeline.getUserTimeline( @@ -68,7 +67,7 @@ export default apiRoute((app) => context.req.url, ); - return jsonResponse( + return context.json( objects.map((user) => user.toApi()), 200, { diff --git a/server/api/api/v1/statuses/:id/source.ts b/server/api/api/v1/statuses/:id/source.ts index 9bdc1368..43a6dbe1 100644 --- a/server/api/api/v1/statuses/:id/source.ts +++ b/server/api/api/v1/statuses/:id/source.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import type { StatusSource as ApiStatusSource } from "@lysand-org/client/types"; import { z } from "zod"; @@ -38,16 +37,16 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const status = await Note.fromId(id, user.id); if (!status?.isViewableByUser(user)) { - return errorResponse("Record not found", 404); + return context.json({ error: "Record not found" }, 404); } - return jsonResponse({ + return context.json({ id: status.id, // TODO: Give real source for spoilerText spoiler_text: status.data.spoilerText, diff --git a/server/api/api/v1/statuses/:id/unfavourite.ts b/server/api/api/v1/statuses/:id/unfavourite.ts index affad5e4..21236fcf 100644 --- a/server/api/api/v1/statuses/:id/unfavourite.ts +++ b/server/api/api/v1/statuses/:id/unfavourite.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { deleteLike } from "~/classes/functions/like"; @@ -38,13 +37,13 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const note = await Note.fromId(id, user.id); if (!note?.isViewableByUser(user)) { - return errorResponse("Record not found", 404); + return context.json({ error: "Record not found" }, 404); } await deleteLike(user, note); @@ -52,10 +51,10 @@ export default apiRoute((app) => const newNote = await Note.fromId(id, user.id); if (!newNote) { - return errorResponse("Record not found", 404); + return context.json({ error: "Record not found" }, 404); } - return jsonResponse(await newNote.toApi(user)); + return context.json(await newNote.toApi(user)); }, ), ); diff --git a/server/api/api/v1/statuses/:id/unpin.ts b/server/api/api/v1/statuses/:id/unpin.ts index f8482d7b..252b8580 100644 --- a/server/api/api/v1/statuses/:id/unpin.ts +++ b/server/api/api/v1/statuses/:id/unpin.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { RolePermissions } from "~/drizzle/schema"; @@ -37,26 +36,26 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const status = await Note.fromId(id, user.id); if (!status) { - return errorResponse("Record not found", 404); + return context.json({ error: "Record not found" }, 404); } if (status.author.id !== user.id) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } await user.unpin(status); if (!status) { - return errorResponse("Record not found", 404); + return context.json({ error: "Record not found" }, 404); } - return jsonResponse(await status.toApi(user)); + return context.json(await status.toApi(user)); }, ), ); diff --git a/server/api/api/v1/statuses/:id/unreblog.ts b/server/api/api/v1/statuses/:id/unreblog.ts index a1f024af..d26563e8 100644 --- a/server/api/api/v1/statuses/:id/unreblog.ts +++ b/server/api/api/v1/statuses/:id/unreblog.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, eq } from "drizzle-orm"; import { z } from "zod"; @@ -39,14 +38,14 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const foundStatus = await Note.fromId(id, user.id); // Check if user is authorized to view this status (if it's private) if (!foundStatus?.isViewableByUser(user)) { - return errorResponse("Record not found", 404); + return context.json({ error: "Record not found" }, 404); } const existingReblog = await Note.fromSql( @@ -59,7 +58,7 @@ export default apiRoute((app) => ); if (!existingReblog) { - return errorResponse("Not already reblogged", 422); + return context.json({ error: "Not already reblogged" }, 422); } await existingReblog.delete(); @@ -71,10 +70,10 @@ export default apiRoute((app) => const newNote = await Note.fromId(id, user.id); if (!newNote) { - return errorResponse("Record not found", 404); + return context.json({ error: "Record not found" }, 404); } - return jsonResponse(await newNote.toApi(user)); + return context.json(await newNote.toApi(user)); }, ), ); diff --git a/server/api/api/v1/statuses/index.test.ts b/server/api/api/v1/statuses/index.test.ts index 48cef09b..81816f2a 100644 --- a/server/api/api/v1/statuses/index.test.ts +++ b/server/api/api/v1/statuses/index.test.ts @@ -173,7 +173,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const object = (await response.json()) as ApiStatus; @@ -198,7 +200,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const object = (await response.json()) as ApiStatus; @@ -237,7 +241,9 @@ describe(meta.route, () => { ); expect(response2.status).toBe(200); - expect(response2.headers.get("content-type")).toBe("application/json"); + expect(response2.headers.get("content-type")).toContain( + "application/json", + ); const object2 = (await response2.json()) as ApiStatus; @@ -276,7 +282,9 @@ describe(meta.route, () => { ); expect(response2.status).toBe(200); - expect(response2.headers.get("content-type")).toBe("application/json"); + expect(response2.headers.get("content-type")).toContain( + "application/json", + ); const object2 = (await response2.json()) as ApiStatus; @@ -299,7 +307,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const object = (await response.json()) as ApiStatus; @@ -326,7 +336,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -357,7 +367,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -388,7 +398,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -416,7 +426,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -442,7 +452,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); diff --git a/server/api/api/v1/statuses/index.ts b/server/api/api/v1/statuses/index.ts index 596df5a7..3acefb50 100644 --- a/server/api/api/v1/statuses/index.ts +++ b/server/api/api/v1/statuses/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError, jsonOrForm } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import ISO6391 from "iso-639-1"; import { z } from "zod"; @@ -112,7 +111,7 @@ export default apiRoute((app) => const { user, application } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const { @@ -132,17 +131,23 @@ export default apiRoute((app) => const foundAttachments = await Attachment.fromIds(media_ids); if (foundAttachments.length !== media_ids.length) { - return errorResponse("Invalid media IDs", 422); + return context.json({ error: "Invalid media IDs" }, 422); } } // Check that in_reply_to_id and quote_id are real posts if provided if (in_reply_to_id && !(await Note.fromId(in_reply_to_id))) { - return errorResponse("Invalid in_reply_to_id (not found)", 422); + return context.json( + { error: "Invalid in_reply_to_id (not found)" }, + 422, + ); } if (quote_id && !(await Note.fromId(quote_id))) { - return errorResponse("Invalid quote_id (not found)", 422); + return context.json( + { error: "Invalid quote_id (not found)" }, + 422, + ); } const newNote = await Note.fromData({ @@ -165,7 +170,7 @@ export default apiRoute((app) => await newNote.federateToUsers(); } - return jsonResponse(await newNote.toApi(user)); + return context.json(await newNote.toApi(user)); }, ), ); diff --git a/server/api/api/v1/timelines/home.test.ts b/server/api/api/v1/timelines/home.test.ts index 12685a0b..9eb0d318 100644 --- a/server/api/api/v1/timelines/home.test.ts +++ b/server/api/api/v1/timelines/home.test.ts @@ -33,7 +33,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const objects = (await response.json()) as ApiStatus[]; @@ -50,7 +52,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const objects = (await response.json()) as ApiStatus[]; @@ -98,7 +102,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -150,7 +154,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -199,7 +203,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); diff --git a/server/api/api/v1/timelines/home.ts b/server/api/api/v1/timelines/home.ts index fdfb3a62..32e0fcfb 100644 --- a/server/api/api/v1/timelines/home.ts +++ b/server/api/api/v1/timelines/home.ts @@ -5,7 +5,6 @@ import { handleZodError, idValidator, } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, eq, gt, gte, lt, or, sql } from "drizzle-orm"; import { z } from "zod"; @@ -54,7 +53,7 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const { objects, link } = await Timeline.getNoteTimeline( @@ -76,7 +75,7 @@ export default apiRoute((app) => user.id, ); - return jsonResponse( + return context.json( await Promise.all( objects.map(async (note) => note.toApi(user)), ), diff --git a/server/api/api/v1/timelines/public.test.ts b/server/api/api/v1/timelines/public.test.ts index b5533aad..3d5e9bed 100644 --- a/server/api/api/v1/timelines/public.test.ts +++ b/server/api/api/v1/timelines/public.test.ts @@ -25,7 +25,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const objects = (await response.json()) as ApiStatus[]; @@ -42,7 +44,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const objects = (await response.json()) as ApiStatus[]; @@ -72,7 +76,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const objects = (await response.json()) as ApiStatus[]; @@ -102,7 +108,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const objects = (await response.json()) as ApiStatus[]; @@ -143,7 +151,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -195,7 +203,7 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -245,7 +253,9 @@ describe(meta.route, () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const objects = (await response.json()) as ApiStatus[]; diff --git a/server/api/api/v1/timelines/public.ts b/server/api/api/v1/timelines/public.ts index f5c660df..edfe49b5 100644 --- a/server/api/api/v1/timelines/public.ts +++ b/server/api/api/v1/timelines/public.ts @@ -5,7 +5,6 @@ import { handleZodError, idValidator, } from "@/api"; -import { jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, gt, gte, lt, sql } from "drizzle-orm"; import { z } from "zod"; @@ -94,7 +93,7 @@ export default apiRoute((app) => user?.id, ); - return jsonResponse( + return context.json( await Promise.all( objects.map(async (note) => note.toApi(user)), ), diff --git a/server/api/api/v2/filters/:id/index.ts b/server/api/api/v2/filters/:id/index.ts index e318313d..4469f920 100644 --- a/server/api/api/v2/filters/:id/index.ts +++ b/server/api/api/v2/filters/:id/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError, jsonOrForm } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, eq, inArray } from "drizzle-orm"; import { z } from "zod"; @@ -82,7 +81,7 @@ export default apiRoute((app) => const { id } = context.req.valid("param"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } const userFilter = await db.query.Filters.findFirst({ @@ -94,12 +93,12 @@ export default apiRoute((app) => }); if (!userFilter) { - return errorResponse("Filter not found", 404); + return context.json({ error: "Filter not found" }, 404); } switch (context.req.method) { case "GET": { - return jsonResponse({ + return context.json({ id: userFilter.id, title: userFilter.title, context: userFilter.context, @@ -187,10 +186,13 @@ export default apiRoute((app) => }); if (!updatedFilter) { - return errorResponse("Failed to update filter", 500); + return context.json( + { error: "Failed to update filter" }, + 500, + ); } - return jsonResponse({ + return context.json({ id: updatedFilter.id, title: updatedFilter.title, context: updatedFilter.context, @@ -216,7 +218,7 @@ export default apiRoute((app) => ), ); - return jsonResponse({}); + return context.json({}); } } }, diff --git a/server/api/api/v2/filters/index.ts b/server/api/api/v2/filters/index.ts index b22a0792..a51fa2a7 100644 --- a/server/api/api/v2/filters/index.ts +++ b/server/api/api/v2/filters/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError, jsonOrForm } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { db } from "~/drizzle/db"; @@ -69,7 +68,7 @@ export default apiRoute((app) => const { user } = context.req.valid("header"); if (!user) { - return errorResponse("Unauthorized", 401); + return context.json({ error: "Unauthorized" }, 401); } switch (context.req.method) { case "GET": { @@ -80,7 +79,7 @@ export default apiRoute((app) => }, }); - return jsonResponse( + return context.json( userFilters.map((filter) => ({ id: filter.id, title: filter.title, @@ -103,8 +102,8 @@ export default apiRoute((app) => case "POST": { const form = context.req.valid("json"); if (!form) { - return errorResponse( - "Missing required Form fields", + return context.json( + { error: "Missing required fields" }, 422, ); } @@ -118,8 +117,10 @@ export default apiRoute((app) => } = form; if (!title || ctx?.length === 0) { - return errorResponse( - "Missing required fields (title and context)", + return context.json( + { + error: "Missing required fields (title and context)", + }, 422, ); } @@ -140,7 +141,10 @@ export default apiRoute((app) => )[0]; if (!newFilter) { - return errorResponse("Failed to create filter", 500); + return context.json( + { error: "Failed to create filter" }, + 500, + ); } const insertedKeywords = @@ -158,7 +162,7 @@ export default apiRoute((app) => .returning() : []; - return jsonResponse({ + return context.json({ id: newFilter.id, title: newFilter.title, context: newFilter.context, diff --git a/server/api/api/v2/instance/index.ts b/server/api/api/v2/instance/index.ts index bcb6ac20..9d515c3d 100644 --- a/server/api/api/v2/instance/index.ts +++ b/server/api/api/v2/instance/index.ts @@ -1,5 +1,5 @@ import { apiRoute, applyConfig } from "@/api"; -import { jsonResponse, proxyUrl } from "@/response"; +import { proxyUrl } from "@/response"; import type { Instance as ApiInstance } from "@lysand-org/client/types"; import { and, eq, isNull } from "drizzle-orm"; import { Users } from "~/drizzle/schema"; @@ -20,7 +20,7 @@ export const meta = applyConfig({ }); export default apiRoute((app) => - app.on(meta.allowedMethods, meta.route, async () => { + app.on(meta.allowedMethods, meta.route, async (context) => { // Get software version from package.json const version = manifest.version; @@ -33,7 +33,7 @@ export default apiRoute((app) => ); // TODO: fill in more values - return jsonResponse({ + return context.json({ domain: new URL(config.http.base_url).hostname, title: config.instance.name, version: "4.3.0-alpha.3+glitch", diff --git a/server/api/api/v2/media/index.ts b/server/api/api/v2/media/index.ts index e8852198..6cee7a10 100644 --- a/server/api/api/v2/media/index.ts +++ b/server/api/api/v2/media/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, auth, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import sharp from "sharp"; import { z } from "zod"; @@ -46,8 +45,10 @@ export default apiRoute((app) => const { file, thumbnail, description } = context.req.valid("form"); if (file.size > config.validation.max_media_size) { - return errorResponse( - `File too large, max size is ${config.validation.max_media_size} bytes`, + return context.json( + { + error: `File too large, max size is ${config.validation.max_media_size} bytes`, + }, 413, ); } @@ -56,7 +57,7 @@ export default apiRoute((app) => config.validation.enforce_mime_types && !config.validation.allowed_mime_types.includes(file.type) ) { - return errorResponse("Invalid file type", 415); + return context.json({ error: "Invalid file type" }, 415); } const sha256 = new Bun.SHA256(); @@ -96,10 +97,10 @@ export default apiRoute((app) => // TODO: Add job to process videos and other media if (isImage) { - return jsonResponse(newAttachment.toApi()); + return context.json(newAttachment.toApi()); } - return jsonResponse( + return context.json( { ...newAttachment.toApi(), url: null, diff --git a/server/api/api/v2/search/index.ts b/server/api/api/v2/search/index.ts index 5fda52d5..b59138ba 100644 --- a/server/api/api/v2/search/index.ts +++ b/server/api/api/v2/search/index.ts @@ -6,7 +6,6 @@ import { parseUserAddress, userAddressValidator, } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, eq, inArray, sql } from "drizzle-orm"; import { z } from "zod"; @@ -63,19 +62,21 @@ export default apiRoute((app) => context.req.valid("query"); if (!self && (resolve || offset)) { - return errorResponse( - "Cannot use resolve or offset without being authenticated", + return context.json( + { + error: "Cannot use resolve or offset without being authenticated", + }, 401, ); } if (!q) { - return errorResponse("Query is required", 400); + return context.json({ error: "Query is required" }, 400); } if (!config.sonic.enabled) { - return errorResponse( - "Search is not enabled by your server administrator", + return context.json( + { error: "Search is not enabled on this server" }, 501, ); } @@ -119,7 +120,7 @@ export default apiRoute((app) => : null; if (account) { - return jsonResponse({ + return context.json({ accounts: [account.toApi()], statuses: [], hashtags: [], @@ -141,7 +142,7 @@ export default apiRoute((app) => const newUser = await User.resolve(uri); if (newUser) { - return jsonResponse({ + return context.json({ accounts: [newUser.toApi()], statuses: [], hashtags: [], @@ -210,7 +211,7 @@ export default apiRoute((app) => ) : []; - return jsonResponse({ + return context.json({ accounts: accounts.map((account) => account.toApi()), statuses: await Promise.all( statuses.map((status) => status.toApi(self)), diff --git a/server/api/media/:hash/:name/index.ts b/server/api/media/:hash/:name/index.ts index 2957b00c..b6a4968f 100644 --- a/server/api/media/:hash/:name/index.ts +++ b/server/api/media/:hash/:name/index.ts @@ -1,5 +1,5 @@ import { apiRoute, applyConfig, handleZodError } from "@/api"; -import { errorResponse, response } from "@/response"; +import { response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; @@ -50,7 +50,7 @@ export default apiRoute((app) => const buffer = await file.arrayBuffer(); if (!(await file.exists())) { - return errorResponse("File not found", 404); + return context.json({ error: "File not found" }, 404); } // Can't directly copy file into Response because this crashes Bun for now diff --git a/server/api/media/proxy/:id.ts b/server/api/media/proxy/:id.ts index 39c26961..57542c05 100644 --- a/server/api/media/proxy/:id.ts +++ b/server/api/media/proxy/:id.ts @@ -1,5 +1,5 @@ import { apiRoute, applyConfig, handleZodError } from "@/api"; -import { errorResponse, response } from "@/response"; +import { response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { config } from "~/packages/config-manager"; @@ -34,8 +34,8 @@ export default apiRoute((app) => // Check if URL is valid if (!URL.canParse(id)) { - return errorResponse( - "Invalid URL (it should be encoded as base64url", + return context.json( + { error: "Invalid URL (it should be encoded as base64url" }, 400, ); } diff --git a/server/api/oauth/sso/:issuer/callback/index.ts b/server/api/oauth/sso/:issuer/callback/index.ts index 25a08ffb..a3103929 100644 --- a/server/api/oauth/sso/:issuer/callback/index.ts +++ b/server/api/oauth/sso/:issuer/callback/index.ts @@ -1,6 +1,6 @@ import { apiRoute, applyConfig, handleZodError } from "@/api"; import { randomString } from "@/math"; -import { errorResponse, response } from "@/response"; +import { response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, eq, isNull } from "drizzle-orm"; import { SignJWT } from "jose"; @@ -232,7 +232,7 @@ export default apiRoute((app) => } if (!flow.application) { - return errorResponse("Application not found", 500); + return context.json({ error: "Application not found" }, 500); } const code = randomString(32, "hex"); diff --git a/server/api/oauth/token/index.ts b/server/api/oauth/token/index.ts index 58cab65b..2d718557 100644 --- a/server/api/oauth/token/index.ts +++ b/server/api/oauth/token/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, handleZodError, jsonOrForm } from "@/api"; -import { jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { eq } from "drizzle-orm"; import { z } from "zod"; @@ -51,15 +50,6 @@ export const schemas = { }), }; -const returnError = (error: string, description: string) => - jsonResponse( - { - error, - error_description: description, - }, - 401, - ); - export default apiRoute((app) => app.on( meta.allowedMethods, @@ -73,23 +63,32 @@ export default apiRoute((app) => switch (grant_type) { case "authorization_code": { if (!code) { - return returnError( - "invalid_request", - "Code is required", + return context.json( + { + error: "invalid_request", + error_description: "Code is required", + }, + 401, ); } if (!redirect_uri) { - return returnError( - "invalid_request", - "Redirect URI is required", + return context.json( + { + error: "invalid_request", + error_description: "Redirect URI is required", + }, + 401, ); } if (!client_id) { - return returnError( - "invalid_client", - "Client ID is required", + return context.json( + { + error: "invalid_request", + error_description: "Client ID is required", + }, + 401, ); } @@ -100,9 +99,12 @@ export default apiRoute((app) => }); if (!client || client.secret !== client_secret) { - return returnError( - "invalid_client", - "Invalid client credentials", + return context.json( + { + error: "invalid_client", + error_description: "Invalid client credentials", + }, + 401, ); } @@ -116,7 +118,13 @@ export default apiRoute((app) => }); if (!token) { - return returnError("invalid_grant", "Code not found"); + return context.json( + { + error: "invalid_grant", + error_description: "Code not found", + }, + 401, + ); } // Invalidate the code @@ -125,7 +133,7 @@ export default apiRoute((app) => .set({ code: null }) .where(eq(Tokens.id, token.id)); - return jsonResponse({ + return context.json({ access_token: token.accessToken, token_type: "Bearer", expires_in: token.expiresAt @@ -145,9 +153,12 @@ export default apiRoute((app) => } } - return returnError( - "unsupported_grant_type", - "Unsupported grant type", + return context.json( + { + error: "unsupported_grant_type", + error_description: "Unsupported grant type", + }, + 401, ); }, ), diff --git a/server/api/objects/:id/index.ts b/server/api/objects/:id/index.ts index bdb21b64..c9f3ed0a 100644 --- a/server/api/objects/:id/index.ts +++ b/server/api/objects/:id/index.ts @@ -1,5 +1,5 @@ import { apiRoute, applyConfig, handleZodError } from "@/api"; -import { errorResponse, response } from "@/response"; +import { response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import type { Entity } from "@lysand-org/federation/types"; import { and, eq, inArray, sql } from "drizzle-orm"; @@ -60,7 +60,7 @@ export default apiRoute((app) => if (foundObject) { if (!foundObject.isViewableByUser(null)) { - return errorResponse("Object not found", 404); + return context.json({ error: "Object not found" }, 404); } } else { foundObject = @@ -78,12 +78,12 @@ export default apiRoute((app) => } if (!(foundObject && apiObject)) { - return errorResponse("Object not found", 404); + return context.json({ error: "Object not found" }, 404); } if (foundAuthor?.isRemote()) { - return errorResponse( - "Cannot view objects from remote instances", + return context.json( + { error: "Cannot view objects from remote instances" }, 403, ); } diff --git a/server/api/users/:uuid/inbox/index.ts b/server/api/users/:uuid/inbox/index.ts index 4a868c10..55cf5b0b 100644 --- a/server/api/users/:uuid/inbox/index.ts +++ b/server/api/users/:uuid/inbox/index.ts @@ -1,5 +1,5 @@ import { apiRoute, applyConfig, debugRequest, handleZodError } from "@/api"; -import { errorResponse, jsonResponse, response } from "@/response"; +import { response } from "@/response"; import { sentry } from "@/sentry"; import { zValidator } from "@hono/zod-validator"; import { getLogger } from "@logtape/logtape"; @@ -75,12 +75,12 @@ export default apiRoute((app) => const user = await User.fromId(uuid); if (!user) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } if (user.isRemote()) { - return errorResponse( - "Cannot view users from remote instances", + return context.json( + { error: "Cannot view users from remote instances" }, 403, ); } @@ -98,8 +98,10 @@ export default apiRoute((app) => if (token) { // Request is bridge request if (token !== config.federation.bridge.token) { - return errorResponse( - "An invalid token was passed in the Authorization header. Please use the correct token, or remove the Authorization header.", + return context.json( + { + error: "An invalid token was passed in the Authorization header. Please use the correct token, or remove the Authorization header.", + }, 401, ); } @@ -116,8 +118,10 @@ export default apiRoute((app) => } } } else { - return errorResponse( - "Request IP address is not available", + return context.json( + { + error: "Request IP address is not available", + }, 500, ); } @@ -146,7 +150,10 @@ export default apiRoute((app) => // Verify request signature if (checkSignature) { if (!sender) { - return errorResponse("Could not resolve keyId", 400); + return context.json( + { error: "Could not resolve keyId" }, + 400, + ); } if (config.debug.federation) { @@ -186,7 +193,7 @@ export default apiRoute((app) => }); if (!isValid) { - return errorResponse("Invalid signature", 400); + return context.json({ error: "Invalid signature" }, 400); } } @@ -199,7 +206,10 @@ export default apiRoute((app) => const account = await User.resolve(note.author); if (!account) { - return errorResponse("Author not found", 404); + return context.json( + { error: "Author not found" }, + 404, + ); } const newStatus = await Note.fromVersia( @@ -212,7 +222,10 @@ export default apiRoute((app) => }); if (!newStatus) { - return errorResponse("Failed to add status", 500); + return context.json( + { error: "Failed to add status" }, + 500, + ); } return response("Note created", 201); @@ -221,7 +234,10 @@ export default apiRoute((app) => const account = await User.resolve(follow.author); if (!account) { - return errorResponse("Author not found", 400); + return context.json( + { error: "Author not found" }, + 400, + ); } const foundRelationship = @@ -260,7 +276,10 @@ export default apiRoute((app) => const account = await User.resolve(followAccept.author); if (!account) { - return errorResponse("Author not found", 400); + return context.json( + { error: "Author not found" }, + 400, + ); } const foundRelationship = @@ -287,7 +306,10 @@ export default apiRoute((app) => const account = await User.resolve(followReject.author); if (!account) { - return errorResponse("Author not found", 400); + return context.json( + { error: "Author not found" }, + 400, + ); } const foundRelationship = @@ -338,14 +360,18 @@ export default apiRoute((app) => await user.delete(); return response("Account deleted", 200); } - return errorResponse( - "Cannot delete other users than self", + return context.json( + { + error: "Cannot delete other users than self", + }, 400, ); } - return errorResponse( - `Deletion of object ${toDelete} not implemented`, + return context.json( + { + error: `Deletetion of object ${toDelete} not implemented`, + }, 400, ); }, @@ -356,7 +382,10 @@ export default apiRoute((app) => ); if (!updatedAccount) { - return errorResponse("Failed to update user", 500); + return context.json( + { error: "Failed to update user" }, + 500, + ); } return response("User refreshed", 200); @@ -372,7 +401,10 @@ export default apiRoute((app) => // Refetch note if (!note) { - return errorResponse("Note not found", 404); + return context.json( + { error: "Note not found" }, + 404, + ); } await note.updateFromRemote(); @@ -385,14 +417,23 @@ export default apiRoute((app) => return result; } - return errorResponse("Object has not been implemented", 400); + return context.json( + { error: "Object has not been implemented" }, + 400, + ); } catch (e) { if (isValidationError(e)) { - return errorResponse((e as ValidationError).message, 400); + return context.json( + { + error: "Failed to process request", + error_description: (e as ValidationError).message, + }, + 400, + ); } logger.error`${e}`; sentry?.captureException(e); - return jsonResponse( + return context.json( { error: "Failed to process request", message: (e as Error).message, diff --git a/server/api/users/:uuid/index.ts b/server/api/users/:uuid/index.ts index d59cf294..3ad4119b 100644 --- a/server/api/users/:uuid/index.ts +++ b/server/api/users/:uuid/index.ts @@ -1,5 +1,5 @@ import { apiRoute, applyConfig, handleZodError } from "@/api"; -import { errorResponse, redirect, response } from "@/response"; +import { redirect, response } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { z } from "zod"; import { config } from "~/packages/config-manager"; @@ -45,12 +45,12 @@ export default apiRoute((app) => : await User.fromId(uuid); if (!user) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } if (user.isRemote()) { - return errorResponse( - "Cannot view users from remote instances", + return context.json( + { error: "Cannot view users from remote instances" }, 403, ); } diff --git a/server/api/users/:uuid/outbox/index.ts b/server/api/users/:uuid/outbox/index.ts index 735498c5..05e675ac 100644 --- a/server/api/users/:uuid/outbox/index.ts +++ b/server/api/users/:uuid/outbox/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig, handleZodError } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { and, count, eq, inArray } from "drizzle-orm"; import { z } from "zod"; @@ -44,12 +43,12 @@ export default apiRoute((app) => const author = await User.fromId(uuid); if (!author) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } if (author.isRemote()) { - return errorResponse( - "Cannot view users from remote instances", + return context.json( + { error: "Cannot view users from remote instances" }, 403, ); } @@ -80,7 +79,7 @@ export default apiRoute((app) => ) )[0].count; - return jsonResponse({ + return context.json({ first: new URL( `/users/${uuid}/outbox?page=1`, config.http.base_url, diff --git a/server/api/well-known/jwks/index.ts b/server/api/well-known/jwks/index.ts index 071ed6c7..0ad56e12 100644 --- a/server/api/well-known/jwks/index.ts +++ b/server/api/well-known/jwks/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig } from "@/api"; -import { jsonResponse } from "@/response"; import { exportJWK } from "jose"; import { config } from "~/packages/config-manager"; @@ -16,7 +15,7 @@ export const meta = applyConfig({ }); export default apiRoute((app) => - app.on(meta.allowedMethods, meta.route, async () => { + app.on(meta.allowedMethods, meta.route, async (context) => { const publicKey = await crypto.subtle.importKey( "spki", Buffer.from(config.oidc.jwt_key.split(";")[1], "base64"), @@ -30,7 +29,7 @@ export default apiRoute((app) => // Remove the private key jwk.d = undefined; - return jsonResponse({ + return context.json({ keys: [ { ...jwk, diff --git a/server/api/well-known/nodeinfo/2.0/index.ts b/server/api/well-known/nodeinfo/2.0/index.ts index a48c1773..a753f259 100644 --- a/server/api/well-known/nodeinfo/2.0/index.ts +++ b/server/api/well-known/nodeinfo/2.0/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig } from "@/api"; -import { jsonResponse } from "@/response"; import manifest from "~/package.json"; export const meta = applyConfig({ @@ -15,8 +14,8 @@ export const meta = applyConfig({ }); export default apiRoute((app) => - app.on(meta.allowedMethods, meta.route, () => { - return jsonResponse({ + app.on(meta.allowedMethods, meta.route, (context) => { + return context.json({ version: "2.0", software: { name: "versia-server", version: manifest.version }, protocols: ["versia"], diff --git a/server/api/well-known/openid-configuration/index.ts b/server/api/well-known/openid-configuration/index.ts index 8c386010..deaf55ea 100644 --- a/server/api/well-known/openid-configuration/index.ts +++ b/server/api/well-known/openid-configuration/index.ts @@ -1,5 +1,4 @@ import { apiRoute, applyConfig } from "@/api"; -import { jsonResponse } from "@/response"; import { config } from "~/packages/config-manager"; export const meta = applyConfig({ @@ -15,9 +14,9 @@ export const meta = applyConfig({ }); export default apiRoute((app) => - app.on(meta.allowedMethods, meta.route, () => { + app.on(meta.allowedMethods, meta.route, (context) => { const baseUrl = new URL(config.http.base_url); - return jsonResponse({ + return context.json({ issuer: baseUrl.origin.toString(), authorization_endpoint: `${baseUrl.origin}/oauth/authorize`, token_endpoint: `${baseUrl.origin}/oauth/token`, diff --git a/server/api/well-known/versia.ts b/server/api/well-known/versia.ts index aba53803..86bddeb6 100644 --- a/server/api/well-known/versia.ts +++ b/server/api/well-known/versia.ts @@ -1,6 +1,5 @@ import { apiRoute, applyConfig } from "@/api"; import { urlToContentFormat } from "@/content_types"; -import { jsonResponse } from "@/response"; import type { ServerMetadata } from "@lysand-org/federation/types"; import pkg from "~/package.json"; import { config } from "~/packages/config-manager"; @@ -18,8 +17,8 @@ export const meta = applyConfig({ }); export default apiRoute((app) => - app.on(meta.allowedMethods, meta.route, () => { - return jsonResponse({ + app.on(meta.allowedMethods, meta.route, (context) => { + return context.json({ type: "ServerMetadata", name: config.instance.name, version: pkg.version, diff --git a/server/api/well-known/webfinger/index.ts b/server/api/well-known/webfinger/index.ts index 80ad2290..1d5603b1 100644 --- a/server/api/well-known/webfinger/index.ts +++ b/server/api/well-known/webfinger/index.ts @@ -5,7 +5,6 @@ import { idValidator, webfingerMention, } from "@/api"; -import { errorResponse, jsonResponse } from "@/response"; import { zValidator } from "@hono/zod-validator"; import { getLogger } from "@logtape/logtape"; import type { ResponseError } from "@lysand-org/federation"; @@ -44,8 +43,10 @@ export default apiRoute((app) => // Check if resource is in the correct format (acct:uuid/username@domain) if (!resource.match(webfingerMention)) { - return errorResponse( - "Invalid resource (should be acct:(id or username)@domain)", + return context.json( + { + error: "Invalid resource (should be acct:(id or username)@domain)", + }, 400, ); } @@ -56,7 +57,7 @@ export default apiRoute((app) => // Check if user is a local user if (requestedUser.split("@")[1] !== host) { - return errorResponse("User is a remote user", 404); + return context.json({ error: "User is a remote user" }, 404); } const isUuid = requestedUser.split("@")[0].match(idValidator); @@ -72,7 +73,7 @@ export default apiRoute((app) => ); if (!user) { - return errorResponse("User not found", 404); + return context.json({ error: "User not found" }, 404); } let activityPubUrl = ""; @@ -97,7 +98,7 @@ export default apiRoute((app) => } } - return jsonResponse({ + return context.json({ subject: `acct:${ isUuid ? user.id : user.data.username }@${host}`, diff --git a/tests/api/accounts.test.ts b/tests/api/accounts.test.ts index 82cd8f6a..eb5d2286 100644 --- a/tests/api/accounts.test.ts +++ b/tests/api/accounts.test.ts @@ -48,7 +48,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -75,7 +75,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -129,7 +129,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -160,7 +160,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -183,7 +183,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); const body = (await response.json()) as ApiAccount[]; @@ -214,7 +214,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -245,7 +245,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -276,7 +276,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -307,7 +307,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -336,7 +336,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -371,7 +371,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -397,7 +397,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -428,7 +428,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); }); @@ -450,7 +450,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); diff --git a/tests/api/statuses.test.ts b/tests/api/statuses.test.ts index 694a4599..100cbf8a 100644 --- a/tests/api/statuses.test.ts +++ b/tests/api/statuses.test.ts @@ -43,7 +43,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(202); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -76,7 +76,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -123,7 +123,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -168,7 +168,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -218,7 +218,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -248,7 +248,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -276,7 +276,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -308,7 +308,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); @@ -365,7 +365,7 @@ describe("API Tests", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe( + expect(response.headers.get("content-type")).toContain( "application/json", ); diff --git a/tests/oauth.test.ts b/tests/oauth.test.ts index 6041af04..bf0bc094 100644 --- a/tests/oauth.test.ts +++ b/tests/oauth.test.ts @@ -47,7 +47,9 @@ describe("POST /api/v1/apps/", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const json = await response.json(); @@ -149,7 +151,9 @@ describe("POST /oauth/token/", () => { const json = await response.json(); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); expect(json).toEqual({ access_token: expect.any(String), token_type: "Bearer", @@ -178,7 +182,9 @@ describe("GET /api/v1/apps/verify_credentials", () => { ); expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toBe("application/json"); + expect(response.headers.get("content-type")).toContain( + "application/json", + ); const credentials = (await response.json()) as Partial; diff --git a/utils/api.ts b/utils/api.ts index 8b6e1980..5db6f027 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -1,7 +1,5 @@ -import { errorResponse } from "@/response"; import type { Context, Hono } from "@hono/hono"; import { createMiddleware } from "@hono/hono/factory"; -import type { StatusCode } from "@hono/hono/utils/http-status"; import { validator } from "@hono/hono/validator"; import { getLogger } from "@logtape/logtape"; import { extractParams, verifySolution } from "altcha-lib"; @@ -129,10 +127,15 @@ export const handleZodError = ( result: | { success: true; data?: object } | { success: false; error: z.ZodError; data?: object }, - _context: unknown, -) => { + context: Context, +): Response | undefined => { if (!result.success) { - return errorResponse(fromZodError(result.error).message, 422); + return context.json( + { + error: fromZodError(result.error).message, + }, + 422, + ); } }; @@ -142,28 +145,11 @@ const getAuth = async (value: Record) => { : null; }; -const returnContextError = ( - context: Context, - error: string, - code?: StatusCode, - // @ts-expect-error The return type is too complex for TypeScript to work with, but it's fine since this isn't a library -): ReturnType => { - const templateError = errorResponse(error, code); - - return context.json( - { - error, - }, - code, - templateError.headers.toJSON(), - ); -}; - const checkPermissions = ( auth: AuthData | null, permissionData: ApiRouteMetadata["permissions"], context: Context, -) => { +): Response | undefined => { const userPerms = auth?.user ? auth.user.getAllPermissions() : config.permissions.anonymous; @@ -176,9 +162,10 @@ const checkPermissions = ( const missingPerms = requiredPerms.filter( (perm) => !userPerms.includes(perm), ); - return returnContextError( - context, - `You do not have the required permissions to access this route. Missing: ${missingPerms.join(", ")}`, + return context.json( + { + error: `You do not have the required permissions to access this route. Missing: ${missingPerms.join(", ")}`, + }, 403, ); } @@ -188,7 +175,13 @@ const checkRouteNeedsAuth = ( auth: AuthData | null, authData: ApiRouteMetadata["auth"], context: Context, -) => { +): + | Response + | { + user: User | null; + token: string | null; + application: Application | null; + } => { if (auth?.user) { return { user: auth.user as User, @@ -200,9 +193,10 @@ const checkRouteNeedsAuth = ( authData.required || authData.methodOverrides?.[context.req.method as HttpVerb] ) { - return returnContextError( - context, - "This route requires authentication.", + return context.json( + { + error: "This route requires authentication.", + }, 401, ); } @@ -217,7 +211,7 @@ const checkRouteNeedsAuth = ( export const checkRouteNeedsChallenge = async ( challengeData: ApiRouteMetadata["challenge"], context: Context, -): Promise> => { +): Promise => { if (!challengeData) { return true; } @@ -225,9 +219,10 @@ export const checkRouteNeedsChallenge = async ( const challengeSolution = context.req.header("X-Challenge-Solution"); if (!challengeSolution) { - return returnContextError( - context, - "This route requires a challenge solution to be sent to it via the X-Challenge-Solution header. Please check the documentation for more information.", + return context.json( + { + error: "This route requires a challenge solution to be sent to it via the X-Challenge-Solution header. Please check the documentation for more information.", + }, 401, ); } @@ -235,9 +230,10 @@ export const checkRouteNeedsChallenge = async ( const { challenge_id } = extractParams(challengeSolution); if (!challenge_id) { - return returnContextError( - context, - "The challenge solution provided is invalid.", + return context.json( + { + error: "The challenge solution provided is invalid.", + }, 401, ); } @@ -247,17 +243,19 @@ export const checkRouteNeedsChallenge = async ( }); if (!challenge) { - return returnContextError( - context, - "The challenge solution provided is invalid.", + return context.json( + { + error: "The challenge solution provided is invalid.", + }, 401, ); } if (new Date(challenge.expiresAt) < new Date()) { - return returnContextError( - context, - "The challenge provided has expired.", + return context.json( + { + error: "The challenge provided has expired.", + }, 401, ); } @@ -268,9 +266,10 @@ export const checkRouteNeedsChallenge = async ( ); if (!isValid) { - return returnContextError( - context, - "The challenge solution provided is incorrect.", + return context.json( + { + error: "The challenge solution provided is incorrect.", + }, 401, ); } @@ -292,6 +291,9 @@ export const auth = ( validator("header", async (value, context) => { const auth = await getAuth(value); + // Only exists for type casting, as otherwise weird errors happen with Hono + const fakeResponse = context.json({}); + // Permissions check if (permissionData) { const permissionCheck = checkPermissions( @@ -300,7 +302,7 @@ export const auth = ( context, ); if (permissionCheck) { - return permissionCheck; + return permissionCheck as typeof fakeResponse; } } @@ -310,11 +312,17 @@ export const auth = ( context, ); if (challengeCheck !== true) { - return challengeCheck; + return challengeCheck as typeof fakeResponse; } } - return checkRouteNeedsAuth(auth, authData, context); + return checkRouteNeedsAuth(auth, authData, context) as + | typeof fakeResponse + | { + user: User | null; + token: string | null; + application: Application | null; + }; }); // Helper function to parse form data diff --git a/utils/response.ts b/utils/response.ts index d491e831..bf04e2b0 100644 --- a/utils/response.ts +++ b/utils/response.ts @@ -36,32 +36,12 @@ export type Json = | Json[] | { [key: string]: Json }; -export const jsonResponse = ( - data: Json, - status = 200, - headers: Record = {}, -) => { - return response(JSON.stringify(data), status, { - "Content-Type": "application/json", - ...headers, - }); -}; - export const xmlResponse = (data: string, status = 200) => { return response(data, status, { "Content-Type": "application/xml", }); }; -export const errorResponse = (error: string, status = 500) => { - return jsonResponse( - { - error: error, - }, - status, - ); -}; - export const redirect = ( url: string | URL, status = 302,