diff --git a/database/entities/Status.ts b/database/entities/Status.ts index b820df77..938796e0 100644 --- a/database/entities/Status.ts +++ b/database/entities/Status.ts @@ -23,7 +23,7 @@ import { applicationToAPI } from "./Application"; import { attachmentToAPI, attachmentToLysand } from "./Attachment"; import { emojiToAPI, emojiToLysand, parseEmojis } from "./Emoji"; import type { UserWithRelations } from "./User"; -import { getUserUri, userToAPI } from "./User"; +import { getUserUri, resolveUser, userToAPI } from "./User"; import { statusAndUserRelations, userRelations } from "./relations"; import { objectToInboxRequest } from "./Federation"; @@ -58,9 +58,84 @@ export const isViewableByUser = (status: Status, user: User | null) => { return user && (status.mentions as User[]).includes(user); }; -export const fetchFromRemote = async ( - uri: string, -): Promise => {}; +export const resolveStatus = async ( + uri?: string, + providedNote?: Lysand.Note, +): Promise => { + if (!uri && !providedNote) { + throw new Error("No URI or note provided"); + } + + // Check if status not already in database + const foundStatus = await client.status.findUnique({ + where: { + uri: uri ?? providedNote?.uri, + }, + include: statusAndUserRelations, + }); + + if (foundStatus) return foundStatus; + + let note: Lysand.Note | null = providedNote ?? null; + + if (uri) { + if (!URL.canParse(uri)) { + throw new Error(`Invalid URI to parse ${uri}`); + } + + const response = await fetch(uri, { + method: "GET", + headers: { + Accept: "application/json", + }, + }); + + note = (await response.json()) as Lysand.Note; + } + + if (!note) { + throw new Error("No note was able to be fetched"); + } + + if (note.type !== "Note") { + throw new Error("Invalid object type"); + } + + if (!note.author) { + throw new Error("Invalid object author"); + } + + const author = await resolveUser(note.author); + + if (!author) { + throw new Error("Invalid object author"); + } + + return await createNewStatus( + author, + note.content ?? { + "text/plain": { + content: "", + }, + }, + note.visibility as APIStatus["visibility"], + note.is_sensitive ?? false, + note.subject ?? "", + [], + note.uri, + await Promise.all( + (note.mentions ?? []) + .map((mention) => resolveUser(mention)) + .filter( + (mention) => mention !== null, + ) as Promise[], + ), + // TODO: Add attachments + [], + note.replies_to ? await resolveStatus(note.replies_to) : undefined, + note.quotes ? await resolveStatus(note.quotes) : undefined, + ); +}; /** * Return all the ancestors of this post, diff --git a/database/entities/User.ts b/database/entities/User.ts index fe7eaf97..3ca5de57 100644 --- a/database/entities/User.ts +++ b/database/entities/User.ts @@ -180,6 +180,26 @@ export const resolveUser = async (uri: string) => { if (foundUser) return foundUser; + // Check if URI is of a local user + if (uri.startsWith(config.http.base_url)) { + const uuid = uri.match( + /[0-9A-F]{8}-[0-9A-F]{4}-[7][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, + ); + + if (!uuid) { + throw new Error( + `URI ${uri} is of a local user, but it could not be parsed`, + ); + } + + return client.user.findUnique({ + where: { + id: uuid[0], + }, + include: userRelations, + }); + } + if (!URL.canParse(uri)) { throw new Error(`Invalid URI to parse ${uri}`); } diff --git a/server/api/users/[uuid]/inbox/index.ts b/server/api/users/[uuid]/inbox/index.ts index 25a66b2c..b727b061 100644 --- a/server/api/users/[uuid]/inbox/index.ts +++ b/server/api/users/[uuid]/inbox/index.ts @@ -3,7 +3,7 @@ import { errorResponse, response } from "@response"; import { client } from "~database/datasource"; import { userRelations } from "~database/entities/relations"; import type * as Lysand from "lysand-types"; -import { createNewStatus } from "~database/entities/Status"; +import { createNewStatus, resolveStatus } from "~database/entities/Status"; import type { APIStatus } from "~types/entities/status"; import { followAcceptToLysand, @@ -132,27 +132,17 @@ export default apiRoute(async (req, matchedRoute, extraData) => { return errorResponse("Author not found", 400); } - await createNewStatus( - account, - note.content ?? { - "text/plain": { - content: "", - }, + const newStatus = await resolveStatus(undefined, note).catch( + (e) => { + console.error(e); + return null; }, - note.visibility as APIStatus["visibility"], - note.is_sensitive ?? false, - note.subject ?? "", - [], - note.uri, - // TODO: Resolve mentions - [], - // TODO: Add attachments - [], - // TODO: Resolve replies and quoting - undefined, - undefined, ); + if (!newStatus) { + return errorResponse("Failed to add status", 500); + } + return response("Note created", 201); } case "Follow": {