mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 16:38:19 +01:00
feat(federation): ✨ Allow objects to be fetched via their ID
This commit is contained in:
parent
895826a5f8
commit
47040ad273
|
|
@ -11,7 +11,7 @@ export type Like = InferSelectModel<typeof like>;
|
|||
/**
|
||||
* 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
|
||||
|
|
|
|||
|
|
@ -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({});
|
||||
});
|
||||
58
server/api/objects/[uuid]/index.ts
Normal file
58
server/api/objects/[uuid]/index.ts
Normal 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);
|
||||
});
|
||||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue