import * as VersiaEntities from "@versia/sdk/entities"; import { config } from "@versia-server/config"; import { and, desc, eq, type InferInsertModel, type InferSelectModel, inArray, type SQL, } from "drizzle-orm"; import { db } from "../tables/db.ts"; import { type Instances, Likes, type Notes, Notifications, type Users, } from "../tables/schema.ts"; import { BaseInterface } from "./base.ts"; import type { User } from "./user.ts"; type LikeType = InferSelectModel & { liker: InferSelectModel & { instance: InferSelectModel | null; }; liked: InferSelectModel & { author: InferSelectModel & { instance: InferSelectModel | null; }; }; }; export class Like extends BaseInterface { public static $type: LikeType; public async reload(): Promise { const reloaded = await Like.fromId(this.data.id); if (!reloaded) { throw new Error("Failed to reload like"); } this.data = reloaded.data; } public static async fromId(id: string | null): Promise { if (!id) { return null; } return await Like.fromSql(eq(Likes.id, id)); } public static async fromIds(ids: string[]): Promise { return await Like.manyFromSql(inArray(Likes.id, ids)); } public static async fromSql( sql: SQL | undefined, orderBy: SQL | undefined = desc(Likes.id), ): Promise { const found = await db.query.Likes.findFirst({ where: sql, orderBy, with: { liked: { with: { author: { with: { instance: true, }, }, }, }, liker: { with: { instance: true, }, }, }, }); if (!found) { return null; } return new Like(found); } public static async manyFromSql( sql: SQL | undefined, orderBy: SQL | undefined = desc(Likes.id), limit?: number, offset?: number, ): Promise { const found = await db.query.Likes.findMany({ where: sql, orderBy, limit, offset, with: { liked: { with: { author: { with: { instance: true, }, }, }, }, liker: { with: { instance: true, }, }, }, }); return found.map((s) => new Like(s)); } public async update(newRole: Partial): Promise { await db.update(Likes).set(newRole).where(eq(Likes.id, this.id)); const updated = await Like.fromId(this.data.id); if (!updated) { throw new Error("Failed to update like"); } return updated.data; } public save(): Promise { return this.update(this.data); } public async delete(): Promise { await db.delete(Likes).where(eq(Likes.id, this.id)); } public static async insert( data: InferInsertModel, ): Promise { const inserted = (await db.insert(Likes).values(data).returning())[0]; const like = await Like.fromId(inserted.id); if (!like) { throw new Error("Failed to insert like"); } return like; } public get id(): string { return this.data.id; } public async clearRelatedNotifications(): Promise { await db .delete(Notifications) .where( and( eq(Notifications.accountId, this.id), eq(Notifications.type, "favourite"), eq(Notifications.notifiedId, this.data.liked.authorId), eq(Notifications.noteId, this.data.liked.id), ), ); } public toVersia(): VersiaEntities.Like { let likedReference = this.data.liked.id; if (this.data.liked.author.instance) { likedReference = `${this.data.liked.author.instance.domain}:${this.data.liked.remoteId}`; } return new VersiaEntities.Like( { id: this.id, author: this.data.liker.id, type: "pub.versia:likes/Like", created_at: this.data.createdAt.toISOString(), liked: likedReference, }, this.data.liker.instance?.domain ?? config.http.base_url.hostname, ); } public unlikeToVersia(unliker?: User): VersiaEntities.Delete { return new VersiaEntities.Delete( { type: "Delete", created_at: new Date().toISOString(), author: unliker ? unliker.id : this.data.liker.id, deleted_type: "pub.versia:likes/Like", deleted: this.id, }, this.data.liker.instance?.domain ?? config.http.base_url.hostname, ); } }