import type { z } from "@hono/zod-openapi"; import type { WebPushSubscription as WebPushSubscriptionSchema } from "@versia/client-ng/schemas"; import { type Token, type User, db } from "@versia/kit/db"; import { PushSubscriptions, Tokens } from "@versia/kit/tables"; import { type InferInsertModel, type InferSelectModel, type SQL, desc, eq, inArray, } from "drizzle-orm"; import { BaseInterface } from "./base.ts"; type PushSubscriptionType = InferSelectModel; export class PushSubscription extends BaseInterface< typeof PushSubscriptions, PushSubscriptionType > { public static $type: PushSubscriptionType; public async reload(): Promise { const reloaded = await PushSubscription.fromId(this.data.id); if (!reloaded) { throw new Error("Failed to reload subscription"); } this.data = reloaded.data; } public static async fromId( id: string | null, ): Promise { if (!id) { return null; } return await PushSubscription.fromSql(eq(PushSubscriptions.id, id)); } public static async fromIds(ids: string[]): Promise { return await PushSubscription.manyFromSql( inArray(PushSubscriptions.id, ids), ); } public static async fromToken( token: Token, ): Promise { return await PushSubscription.fromSql( eq(PushSubscriptions.tokenId, token.id), ); } public static async manyFromUser( user: User, limit?: number, offset?: number, ): Promise { const found = await db .select() .from(PushSubscriptions) .leftJoin(Tokens, eq(Tokens.id, PushSubscriptions.tokenId)) .where(eq(Tokens.userId, user.id)) .limit(limit ?? 9e10) .offset(offset ?? 0); return found.map((s) => new PushSubscription(s.PushSubscriptions)); } public static async fromSql( sql: SQL | undefined, orderBy: SQL | undefined = desc(PushSubscriptions.id), ): Promise { const found = await db.query.PushSubscriptions.findFirst({ where: sql, orderBy, }); if (!found) { return null; } return new PushSubscription(found); } public static async manyFromSql( sql: SQL | undefined, orderBy: SQL | undefined = desc(PushSubscriptions.id), limit?: number, offset?: number, extra?: Parameters[0], ): Promise { const found = await db.query.PushSubscriptions.findMany({ where: sql, orderBy, limit, offset, with: extra?.with, }); return found.map((s) => new PushSubscription(s)); } public async update( newSubscription: Partial, ): Promise { await db .update(PushSubscriptions) .set(newSubscription) .where(eq(PushSubscriptions.id, this.id)); const updated = await PushSubscription.fromId(this.data.id); if (!updated) { throw new Error("Failed to update subscription"); } this.data = updated.data; return updated.data; } public save(): Promise { return this.update(this.data); } public static async clearAllOfToken(token: Token): Promise { await db .delete(PushSubscriptions) .where(eq(PushSubscriptions.tokenId, token.id)); } public async delete(ids?: string[]): Promise { if (Array.isArray(ids)) { await db .delete(PushSubscriptions) .where(inArray(PushSubscriptions.id, ids)); } else { await db .delete(PushSubscriptions) .where(eq(PushSubscriptions.id, this.id)); } } public static async insert( data: InferInsertModel, ): Promise { const inserted = ( await db.insert(PushSubscriptions).values(data).returning() )[0]; const subscription = await PushSubscription.fromId(inserted.id); if (!subscription) { throw new Error("Failed to insert subscription"); } return subscription; } public get id(): string { return this.data.id; } public getAlerts(): z.infer { return { mention: this.data.alerts.mention ?? false, favourite: this.data.alerts.favourite ?? false, reblog: this.data.alerts.reblog ?? false, follow: this.data.alerts.follow ?? false, poll: this.data.alerts.poll ?? false, follow_request: this.data.alerts.follow_request ?? false, status: this.data.alerts.status ?? false, update: this.data.alerts.update ?? false, "admin.sign_up": this.data.alerts["admin.sign_up"] ?? false, "admin.report": this.data.alerts["admin.report"] ?? false, }; } public toApi(): z.infer { return { id: this.data.id, alerts: this.getAlerts(), endpoint: this.data.endpoint, // FIXME: Add real key server_key: "", }; } }