diff --git a/database/entities/Like.ts b/database/entities/Like.ts index e44c38e3..e3b28524 100644 --- a/database/entities/Like.ts +++ b/database/entities/Like.ts @@ -11,7 +11,7 @@ export type Like = InferSelectModel; /** * Represents a Like entity in the database. */ -export const toLysand = (like: Like): Lysand.Like => { +export const likeToLysand = (like: Like): Lysand.Like => { return { id: like.id, // biome-ignore lint/suspicious/noExplicitAny: to be rewritten diff --git a/server/api/object/[uuid]/index.ts b/server/api/object/[uuid]/index.ts deleted file mode 100644 index e11a5c6c..00000000 --- a/server/api/object/[uuid]/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { apiRoute, applyConfig } from "@api"; -import { jsonResponse } from "@response"; - -export const meta = applyConfig({ - allowedMethods: ["GET"], - auth: { - required: false, - }, - ratelimits: { - duration: 60, - max: 500, - }, - route: "/object/:id", -}); - -export default apiRoute(() => { - return jsonResponse({}); -}); diff --git a/server/api/objects/[uuid]/index.ts b/server/api/objects/[uuid]/index.ts new file mode 100644 index 00000000..e687d4e8 --- /dev/null +++ b/server/api/objects/[uuid]/index.ts @@ -0,0 +1,58 @@ +import { apiRoute, applyConfig } from "@api"; +import { errorResponse, jsonResponse } from "@response"; +import { sql } from "drizzle-orm"; +import { likeToLysand, type Like } from "~database/entities/Like"; +import { + findFirstStatuses, + statusToLysand, + type StatusWithRelations, +} from "~database/entities/Status"; +import { db } from "~drizzle/db"; +import type * as Lysand from "lysand-types"; + +export const meta = applyConfig({ + allowedMethods: ["GET"], + auth: { + required: false, + }, + ratelimits: { + duration: 60, + max: 500, + }, + route: "/objects/:id", +}); + +export default apiRoute(async (req, matchedRoute) => { + const uuid = matchedRoute.params.uuid; + + let foundObject: StatusWithRelations | Like | null = null; + let apiObject: Lysand.Entity | null = null; + + foundObject = + (await findFirstStatuses({ + where: (status, { eq, and, inArray }) => + and( + eq(status.id, uuid), + inArray(status.visibility, ["public", "unlisted"]), + ), + })) ?? null; + apiObject = foundObject ? statusToLysand(foundObject) : null; + + if (!foundObject) { + foundObject = + (await db.query.like.findFirst({ + where: (like, { eq, and }) => + and( + eq(like.id, uuid), + sql`EXISTS (SELECT 1 FROM statuses WHERE statuses.id = ${like.likedId} AND statuses.visibility IN ('public', 'unlisted'))`, + ), + })) ?? null; + apiObject = foundObject ? likeToLysand(foundObject) : null; + } + + if (!foundObject) { + return errorResponse("Object not found", 404); + } + + return jsonResponse(foundObject); +}); diff --git a/server/api/objects/note/[uuid]/index.ts b/server/api/objects/note/[uuid]/index.ts deleted file mode 100644 index 4d322eec..00000000 --- a/server/api/objects/note/[uuid]/index.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { apiRoute, applyConfig } from "@api"; -import { errorResponse, jsonResponse } from "@response"; -import { findFirstStatuses, statusToLysand } from "~database/entities/Status"; - -export const meta = applyConfig({ - allowedMethods: ["GET"], - auth: { - required: false, - }, - ratelimits: { - duration: 60, - max: 500, - }, - route: "/objects/note/:uuid", -}); - -export default apiRoute(async (req, matchedRoute, extraData) => { - const uuid = matchedRoute.params.uuid; - - const status = await findFirstStatuses({ - where: (status, { eq }) => eq(status.id, uuid), - }); - - if (!status) { - return errorResponse("Note not found", 404); - } - - const config = await extraData.configManager.getConfig(); - - const output = statusToLysand(status); - - const privateKey = await crypto.subtle.importKey( - "pkcs8", - Uint8Array.from(atob(status.author.privateKey ?? ""), (c) => - c.charCodeAt(0), - ), - "Ed25519", - false, - ["sign"], - ); - - const digest = await crypto.subtle.digest( - "SHA-256", - new TextEncoder().encode(JSON.stringify(output)), - ); - - const userInbox = new URL( - "http://lysand.localhost:8080/users/018ec11c-c6cb-7a67-bd20-a4c81bf42912/inbox", - ); - - const date = new Date(); - - const signature = await crypto.subtle.sign( - "Ed25519", - privateKey, - new TextEncoder().encode( - `(request-target): post ${userInbox.pathname}\n` + - `host: ${userInbox.host}\n` + - `date: ${date.toISOString()}\n` + - `digest: SHA-256=${btoa( - String.fromCharCode(...new Uint8Array(digest)), - )}\n`, - ), - ); - - const signatureBase64 = btoa( - String.fromCharCode(...new Uint8Array(signature)), - ); - - return jsonResponse({ - Date: date.toISOString(), - Origin: "example.com", - Signature: `keyId="https://example.com/users/${status.author.id}",algorithm="ed25519",headers="(request-target) host date digest",signature="${signatureBase64}"`, - post: output, - }); -});