Fix conversion between database and Lysand types

This commit is contained in:
Jesse Wierzbinski 2024-04-09 13:54:10 -10:00
parent 6d0a8a6478
commit 8563c97403
No known key found for this signature in database
7 changed files with 118 additions and 136 deletions

View file

@ -36,7 +36,7 @@ await $`sed -i 's|import("node_modules/|import("./node_modules/|g' dist/*.js`;
// Copy generated Prisma client to dist // Copy generated Prisma client to dist
await $`mkdir -p dist/node_modules/@prisma`; await $`mkdir -p dist/node_modules/@prisma`;
await $`cp -r ${process.cwd()}/node_modules/@prisma dist/node_modules/`; await $`cp -r ${process.cwd()}/node_modules/@prisma dist/node_modules/`;
await $`cp -r ${process.cwd()}/node_modules/.prisma dist/node_modules`; //await $`cp -r ${process.cwd()}/node_modules/.prisma dist/node_modules`;
await $`mkdir -p dist/node_modules/.bin`; await $`mkdir -p dist/node_modules/.bin`;
await $`cp -r ${process.cwd()}/node_modules/.bin/prisma dist/node_modules/.bin`; await $`cp -r ${process.cwd()}/node_modules/.bin/prisma dist/node_modules/.bin`;
await $`cp -r ${process.cwd()}/node_modules/prisma dist/node_modules/`; await $`cp -r ${process.cwd()}/node_modules/prisma dist/node_modules/`;

View file

@ -3,6 +3,7 @@ import type { Config } from "config-manager";
import { MediaBackendType } from "media-manager"; import { MediaBackendType } from "media-manager";
import type { APIAsyncAttachment } from "~types/entities/async_attachment"; import type { APIAsyncAttachment } from "~types/entities/async_attachment";
import type { APIAttachment } from "~types/entities/attachment"; import type { APIAttachment } from "~types/entities/attachment";
import type * as Lysand from "lysand-types";
export const attachmentToAPI = ( export const attachmentToAPI = (
attachment: Attachment, attachment: Attachment,
@ -57,6 +58,28 @@ export const attachmentToAPI = (
}; };
}; };
export const attachmentToLysand = (
attachment: Attachment,
): Lysand.ContentFormat => {
return {
[attachment.mime_type]: {
content: attachment.url,
blurhash: attachment.blurhash ?? undefined,
description: attachment.description ?? undefined,
duration: attachment.duration ?? undefined,
fps: attachment.fps ?? undefined,
height: attachment.height ?? undefined,
size: attachment.size ?? undefined,
hash: attachment.sha256
? {
sha256: attachment.sha256,
}
: undefined,
width: attachment.width ?? undefined,
},
};
};
export const getUrl = (name: string, config: Config) => { export const getUrl = (name: string, config: Config) => {
if (config.media.backend === MediaBackendType.LOCAL) { if (config.media.backend === MediaBackendType.LOCAL) {
return new URL(`/media/${name}`, config.http.base_url).toString(); return new URL(`/media/${name}`, config.http.base_url).toString();

View file

@ -1,7 +1,7 @@
import type { Emoji } from "@prisma/client"; import type { Emoji } from "@prisma/client";
import { client } from "~database/datasource"; import { client } from "~database/datasource";
import type { APIEmoji } from "~types/entities/emoji"; import type { APIEmoji } from "~types/entities/emoji";
import type { Emoji as LysandEmoji } from "~types/lysand/extensions/org.lysand/custom_emojis"; import type * as Lysand from "lysand-types";
/** /**
* Represents an emoji entity in the database. * Represents an emoji entity in the database.
@ -29,7 +29,7 @@ export const parseEmojis = async (text: string): Promise<Emoji[]> => {
}); });
}; };
export const addEmojiIfNotExists = async (emoji: LysandEmoji) => { export const addEmojiIfNotExists = async (emoji: Lysand.Emoji) => {
const existingEmoji = await client.emoji.findFirst({ const existingEmoji = await client.emoji.findFirst({
where: { where: {
shortcode: emoji.name, shortcode: emoji.name,
@ -43,8 +43,8 @@ export const addEmojiIfNotExists = async (emoji: LysandEmoji) => {
data: { data: {
shortcode: emoji.name, shortcode: emoji.name,
url: emoji.url[0].content, url: emoji.url[0].content,
alt: emoji.alt || null, alt: emoji.alt || emoji.url[0].description || undefined,
content_type: emoji.url[0].content_type, content_type: Object.keys(emoji.url)[0],
visible_in_picker: true, visible_in_picker: true,
}, },
}); });
@ -64,34 +64,15 @@ export const emojiToAPI = (emoji: Emoji): APIEmoji => {
}; };
}; };
export const emojiToLysand = (emoji: Emoji): LysandEmoji => { export const emojiToLysand = (emoji: Emoji): Lysand.Emoji => {
return { return {
name: emoji.shortcode, name: emoji.shortcode,
url: [ url: {
{ [emoji.content_type]: {
content: emoji.url, content: emoji.url,
content_type: emoji.content_type, description: emoji.alt || undefined,
},
}, },
],
alt: emoji.alt || undefined, alt: emoji.alt || undefined,
}; };
}; };
/**
* Converts the emoji to an ActivityPub object.
* @returns The ActivityPub object.
*/
export const emojiToActivityPub = (emoji: Emoji): object => {
// replace any with your ActivityPub Emoji type
return {
type: "Emoji",
name: `:${emoji.shortcode}:`,
updated: new Date().toISOString(),
icon: {
type: "Image",
url: emoji.url,
mediaType: emoji.content_type,
alt: emoji.alt || undefined,
},
};
};

View file

@ -1,14 +1,14 @@
import type { Like } from "@prisma/client"; import type { Like } from "@prisma/client";
import { config } from "config-manager"; import { config } from "config-manager";
import { client } from "~database/datasource"; import { client } from "~database/datasource";
import type { Like as LysandLike } from "~types/lysand/Object";
import type { StatusWithRelations } from "./Status"; import type { StatusWithRelations } from "./Status";
import type { UserWithRelations } from "./User"; import type { UserWithRelations } from "./User";
import type * as Lysand from "lysand-types";
/** /**
* Represents a Like entity in the database. * Represents a Like entity in the database.
*/ */
export const toLysand = (like: Like): LysandLike => { export const toLysand = (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
@ -17,7 +17,10 @@ export const toLysand = (like: Like): LysandLike => {
created_at: new Date(like.createdAt).toISOString(), created_at: new Date(like.createdAt).toISOString(),
// biome-ignore lint/suspicious/noExplicitAny: to be rewritten // biome-ignore lint/suspicious/noExplicitAny: to be rewritten
object: (like as any).liked?.uri, object: (like as any).liked?.uri,
uri: new URL(`/actions/${like.id}`, config.http.base_url).toString(), uri: new URL(
`/objects/like/${like.id}`,
config.http.base_url,
).toString(),
}; };
}; };

View file

@ -18,8 +18,9 @@ import { client } from "~database/datasource";
import type { APIAttachment } from "~types/entities/attachment"; import type { APIAttachment } from "~types/entities/attachment";
import type { APIStatus } from "~types/entities/status"; import type { APIStatus } from "~types/entities/status";
import type { LysandPublication, Note } from "~types/lysand/Object"; import type { LysandPublication, Note } from "~types/lysand/Object";
import type * as Lysand from "lysand-types";
import { applicationToAPI } from "./Application"; import { applicationToAPI } from "./Application";
import { attachmentToAPI } from "./Attachment"; import { attachmentToAPI, attachmentToLysand } from "./Attachment";
import { emojiToAPI, emojiToLysand, parseEmojis } from "./Emoji"; import { emojiToAPI, emojiToLysand, parseEmojis } from "./Emoji";
import type { UserWithRelations } from "./User"; import type { UserWithRelations } from "./User";
import { fetchRemoteUser, parseMentionsUris, userToAPI } from "./User"; import { fetchRemoteUser, parseMentionsUris, userToAPI } from "./User";
@ -533,78 +534,33 @@ export const statusToAPI = async (
}; };
}; };
/* export const statusToActivityPub = async ( export const statusToLysand = (status: StatusWithRelations): Lysand.Note => {
status: StatusWithRelations
// user?: UserWithRelations
): Promise<any> => {
// replace any with your ActivityPub type
return {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://mastodon.social/schemas/litepub-0.1.jsonld",
],
id: `${config.http.base_url}/users/${status.authorId}/statuses/${status.id}`,
type: "Note",
summary: status.spoilerText,
content: status.content,
published: new Date(status.createdAt).toISOString(),
url: `${config.http.base_url}/users/${status.authorId}/statuses/${status.id}`,
attributedTo: `${config.http.base_url}/users/${status.authorId}`,
to: ["https://www.w3.org/ns/activitystreams#Public"],
cc: [], // add recipients here
sensitive: status.sensitive,
attachment: (status.attachments ?? []).map(
a => attachmentToActivityPub(a) as ActivityPubAttachment // replace with your function
),
tag: [], // add tags here
replies: {
id: `${config.http.base_url}/users/${status.authorId}/statuses/${status.id}/replies`,
type: "Collection",
totalItems: status._count.replies,
},
likes: {
id: `${config.http.base_url}/users/${status.authorId}/statuses/${status.id}/likes`,
type: "Collection",
totalItems: status._count.likes,
},
shares: {
id: `${config.http.base_url}/users/${status.authorId}/statuses/${status.id}/shares`,
type: "Collection",
totalItems: status._count.reblogs,
},
inReplyTo: status.inReplyToPostId
? `${config.http.base_url}/users/${status.inReplyToPost?.authorId}/statuses/${status.inReplyToPostId}`
: null,
visibility: "public", // adjust as needed
// add more fields as needed
};
}; */
export const statusToLysand = (status: StatusWithRelations): Note => {
return { return {
type: "Note", type: "Note",
created_at: new Date(status.createdAt).toISOString(), created_at: new Date(status.createdAt).toISOString(),
id: status.id, id: status.id,
author: status.authorId, author: status.authorId,
uri: new URL(`/statuses/${status.id}`, config.http.base_url).toString(), uri: new URL(
contents: [ `/objects/note/${status.id}`,
{ config.http.base_url,
).toString(),
content: {
"text/html": {
content: status.content, content: status.content,
content_type: "text/html",
}, },
{ "text/plain": {
// Content converted to plaintext
content: htmlToText(status.content), content: htmlToText(status.content),
content_type: "text/plain",
}, },
], },
// TODO: Add attachments attachments: status.attachments.map((attachment) =>
attachments: [], attachmentToLysand(attachment),
),
is_sensitive: status.sensitive, is_sensitive: status.sensitive,
mentions: status.mentions.map((mention) => mention.uri || ""), mentions: status.mentions.map((mention) => mention.uri || ""),
quotes: status.quotingPost ? [status.quotingPost.uri || ""] : [], quotes: status.quotingPost?.uri ?? undefined,
replies_to: status.inReplyToPostId ? [status.inReplyToPostId] : [], replies_to: status.inReplyToPost?.uri ?? undefined,
subject: status.spoilerText, subject: status.spoilerText,
visibility: status.visibility as Lysand.Visibility,
extensions: { extensions: {
"org.lysand:custom_emojis": { "org.lysand:custom_emojis": {
emojis: status.emojis.map((emoji) => emojiToLysand(emoji)), emojis: status.emojis.map((emoji) => emojiToLysand(emoji)),

View file

@ -6,11 +6,10 @@ import { htmlToText } from "html-to-text";
import { client } from "~database/datasource"; import { client } from "~database/datasource";
import type { APIAccount } from "~types/entities/account"; import type { APIAccount } from "~types/entities/account";
import type { APISource } from "~types/entities/source"; import type { APISource } from "~types/entities/source";
import type { LysandUser } from "~types/lysand/Object"; import type * as Lysand from "lysand-types";
import { addEmojiIfNotExists, emojiToAPI, emojiToLysand } from "./Emoji"; import { addEmojiIfNotExists, emojiToAPI, emojiToLysand } from "./Emoji";
import { addInstanceIfNotExists } from "./Instance"; import { addInstanceIfNotExists } from "./Instance";
import { userRelations } from "./relations"; import { userRelations } from "./relations";
import { getUrl } from "./Attachment";
import { createNewRelationship } from "./Relationship"; import { createNewRelationship } from "./Relationship";
export interface AuthData { export interface AuthData {
@ -147,7 +146,7 @@ export const fetchRemoteUser = async (uri: string) => {
}, },
}); });
const data = (await response.json()) as Partial<LysandUser>; const data = (await response.json()) as Partial<Lysand.User>;
if ( if (
!( !(
@ -155,9 +154,9 @@ export const fetchRemoteUser = async (uri: string) => {
data.username && data.username &&
data.uri && data.uri &&
data.created_at && data.created_at &&
data.disliked && data.dislikes &&
data.featured && data.featured &&
data.liked && data.likes &&
data.followers && data.followers &&
data.following && data.following &&
data.inbox && data.inbox &&
@ -178,9 +177,9 @@ export const fetchRemoteUser = async (uri: string) => {
uri: data.uri, uri: data.uri,
createdAt: new Date(data.created_at), createdAt: new Date(data.created_at),
endpoints: { endpoints: {
disliked: data.disliked, dislikes: data.dislikes,
featured: data.featured, featured: data.featured,
liked: data.liked, likes: data.likes,
followers: data.followers, followers: data.followers,
following: data.following, following: data.following,
inbox: data.inbox, inbox: data.inbox,
@ -444,7 +443,7 @@ export const userToAPI = (
/** /**
* Should only return local users * Should only return local users
*/ */
export const userToLysand = (user: UserWithRelations): LysandUser => { export const userToLysand = (user: UserWithRelations): Lysand.User => {
if (user.instanceId !== null) { if (user.instanceId !== null) {
throw new Error("Cannot convert remote user to Lysand format"); throw new Error("Cannot convert remote user to Lysand format");
} }
@ -452,29 +451,28 @@ export const userToLysand = (user: UserWithRelations): LysandUser => {
return { return {
id: user.id, id: user.id,
type: "User", type: "User",
uri: user.uri || "", uri:
bio: [ user.uri ||
{ new URL(`/users/${user.id}`, config.http.base_url).toString(),
bio: {
"text/html": {
content: user.note, content: user.note,
content_type: "text/html",
}, },
{ "text/plain": {
content: htmlToText(user.note), content: htmlToText(user.note),
content_type: "text/plain",
}, },
], },
created_at: new Date(user.createdAt).toISOString(), created_at: new Date(user.createdAt).toISOString(),
dislikes: new URL(
disliked: new URL( `/users/${user.id}/dislikes`,
`/users/${user.id}/disliked`,
config.http.base_url, config.http.base_url,
).toString(), ).toString(),
featured: new URL( featured: new URL(
`/users/${user.id}/featured`, `/users/${user.id}/featured`,
config.http.base_url, config.http.base_url,
).toString(), ).toString(),
liked: new URL( likes: new URL(
`/users/${user.id}/liked`, `/users/${user.id}/likes`,
config.http.base_url, config.http.base_url,
).toString(), ).toString(),
followers: new URL( followers: new URL(
@ -495,40 +493,35 @@ export const userToLysand = (user: UserWithRelations): LysandUser => {
).toString(), ).toString(),
indexable: false, indexable: false,
username: user.username, username: user.username,
avatar: [ avatar: {
{ [user.avatar.split(".")[1]]: {
content: getAvatarUrl(user, config) || "", content: getAvatarUrl(user, config),
content_type: `image/${user.avatar.split(".")[1]}`, },
},
header: {
[user.header.split(".")[1]]: {
content: getHeaderUrl(user, config),
}, },
],
header: [
{
content: getHeaderUrl(user, config) || "",
content_type: `image/${user.header.split(".")[1]}`,
}, },
],
display_name: user.displayName, display_name: user.displayName,
fields: (user.source as APISource).fields.map((field) => ({ fields: (user.source as APISource).fields.map((field) => ({
key: [ key: {
{ "text/html": {
content: field.name, content: field.name,
content_type: "text/html",
}, },
{ "text/plain": {
content: htmlToText(field.name), content: htmlToText(field.name),
content_type: "text/plain",
}, },
], },
value: [ value: {
{ "text/html": {
content: field.value, content: field.value,
content_type: "text/html",
}, },
{ "text/plain": {
content: htmlToText(field.value), content: htmlToText(field.value),
content_type: "text/plain",
}, },
], },
})), })),
public_key: { public_key: {
actor: new URL( actor: new URL(

View file

@ -44,9 +44,19 @@ export interface Entity {
created_at: string; created_at: string;
uri: string; uri: string;
type: string; type: string;
extensions?: {
"org.lysand:custom_emojis"?: {
emojis: Emoji[];
};
[key: string]: object | undefined;
};
} }
export interface Publication { export interface InlineCustomEmojis {
[key: string]: Emoji;
}
export interface Publication extends Entity {
type: "Note" | "Patch"; type: "Note" | "Patch";
author: string; author: string;
content?: ContentFormat; content?: ContentFormat;
@ -57,6 +67,19 @@ export interface Publication {
subject?: string; subject?: string;
is_sensitive?: boolean; is_sensitive?: boolean;
visibility: Visibility; visibility: Visibility;
extensions?: Entity["extensions"] & {
"org.lysand:reactions"?: {
reactions: string;
};
"org.lysand:polls"?: {
poll: {
options: ContentFormat[];
votes: number[];
multiple_choice?: boolean;
expires_at: string;
};
};
};
} }
export enum Visibility { export enum Visibility {
@ -96,6 +119,9 @@ export interface User extends Entity {
dislikes: string; dislikes: string;
inbox: string; inbox: string;
outbox: string; outbox: string;
extensions?: Entity["extensions"] & {
"org.lysand:vanity"?: VanityExtension;
};
} }
export interface Field { export interface Field {