feat(federation): Allow objects to be fetched via their ID

This commit is contained in:
Jesse Wierzbinski 2024-04-16 11:18:47 -10:00
parent 895826a5f8
commit 47040ad273
No known key found for this signature in database
4 changed files with 59 additions and 95 deletions

View file

@ -11,7 +11,7 @@ export type Like = InferSelectModel<typeof like>;
/** /**
* Represents a Like entity in the database. * Represents a Like entity in the database.
*/ */
export const toLysand = (like: Like): Lysand.Like => { export const likeToLysand = (like: Like): Lysand.Like => {
return { return {
id: like.id, id: like.id,
// biome-ignore lint/suspicious/noExplicitAny: to be rewritten // biome-ignore lint/suspicious/noExplicitAny: to be rewritten

View file

@ -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({});
});

View file

@ -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);
});

View file

@ -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,
});
});