aboutsummaryrefslogtreecommitdiffstats
path: root/packages/trpc/models/backups.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/trpc/models/backups.ts')
-rw-r--r--packages/trpc/models/backups.ts172
1 files changed, 172 insertions, 0 deletions
diff --git a/packages/trpc/models/backups.ts b/packages/trpc/models/backups.ts
new file mode 100644
index 00000000..c7ab99ba
--- /dev/null
+++ b/packages/trpc/models/backups.ts
@@ -0,0 +1,172 @@
+import { TRPCError } from "@trpc/server";
+import { and, desc, eq, lt } from "drizzle-orm";
+import { z } from "zod";
+
+import { assets, backupsTable } from "@karakeep/db/schema";
+import { BackupQueue } from "@karakeep/shared-server";
+import { deleteAsset } from "@karakeep/shared/assetdb";
+import { zBackupSchema } from "@karakeep/shared/types/backups";
+
+import { AuthedContext } from "..";
+
+export class Backup {
+ private constructor(
+ private ctx: AuthedContext,
+ private backup: z.infer<typeof zBackupSchema>,
+ ) {}
+
+ static async fromId(ctx: AuthedContext, backupId: string): Promise<Backup> {
+ const backup = await ctx.db.query.backupsTable.findFirst({
+ where: and(
+ eq(backupsTable.id, backupId),
+ eq(backupsTable.userId, ctx.user.id),
+ ),
+ });
+
+ if (!backup) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Backup not found",
+ });
+ }
+
+ return new Backup(ctx, backup);
+ }
+
+ private static fromData(
+ ctx: AuthedContext,
+ backup: z.infer<typeof zBackupSchema>,
+ ): Backup {
+ return new Backup(ctx, backup);
+ }
+
+ static async getAll(ctx: AuthedContext): Promise<Backup[]> {
+ const backups = await ctx.db.query.backupsTable.findMany({
+ where: eq(backupsTable.userId, ctx.user.id),
+ orderBy: [desc(backupsTable.createdAt)],
+ });
+
+ return backups.map((b) => new Backup(ctx, b));
+ }
+
+ static async create(ctx: AuthedContext): Promise<Backup> {
+ const [backup] = await ctx.db
+ .insert(backupsTable)
+ .values({
+ userId: ctx.user.id,
+ size: 0,
+ bookmarkCount: 0,
+ status: "pending",
+ })
+ .returning();
+ return new Backup(ctx, backup!);
+ }
+
+ async triggerBackgroundJob({
+ delayMs,
+ idempotencyKey,
+ }: { delayMs?: number; idempotencyKey?: string } = {}): Promise<void> {
+ await BackupQueue.enqueue(
+ {
+ userId: this.ctx.user.id,
+ backupId: this.backup.id,
+ },
+ {
+ delayMs,
+ idempotencyKey,
+ },
+ );
+ }
+
+ /**
+ * Generic update method for backup records
+ */
+ async update(
+ data: Partial<{
+ size: number;
+ bookmarkCount: number;
+ status: "pending" | "success" | "failure";
+ assetId: string | null;
+ errorMessage: string | null;
+ }>,
+ ): Promise<void> {
+ await this.ctx.db
+ .update(backupsTable)
+ .set(data)
+ .where(
+ and(
+ eq(backupsTable.id, this.backup.id),
+ eq(backupsTable.userId, this.ctx.user.id),
+ ),
+ );
+
+ // Update local state
+ this.backup = { ...this.backup, ...data };
+ }
+
+ async delete(): Promise<void> {
+ if (this.backup.assetId) {
+ // Delete asset
+ await deleteAsset({
+ userId: this.ctx.user.id,
+ assetId: this.backup.assetId,
+ });
+ }
+
+ await this.ctx.db.transaction(async (db) => {
+ // Delete asset first
+ if (this.backup.assetId) {
+ await db
+ .delete(assets)
+ .where(
+ and(
+ eq(assets.id, this.backup.assetId),
+ eq(assets.userId, this.ctx.user.id),
+ ),
+ );
+ }
+
+ // Delete backup record
+ await db
+ .delete(backupsTable)
+ .where(
+ and(
+ eq(backupsTable.id, this.backup.id),
+ eq(backupsTable.userId, this.ctx.user.id),
+ ),
+ );
+ });
+ }
+
+ /**
+ * Finds backups older than the retention period
+ */
+ static async findOldBackups(
+ ctx: AuthedContext,
+ retentionDays: number,
+ ): Promise<Backup[]> {
+ const cutoffDate = new Date();
+ cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
+
+ const oldBackups = await ctx.db.query.backupsTable.findMany({
+ where: and(
+ eq(backupsTable.userId, ctx.user.id),
+ lt(backupsTable.createdAt, cutoffDate),
+ ),
+ });
+
+ return oldBackups.map((backup) => Backup.fromData(ctx, backup));
+ }
+
+ asPublic(): z.infer<typeof zBackupSchema> {
+ return this.backup;
+ }
+
+ get id() {
+ return this.backup.id;
+ }
+
+ get assetId() {
+ return this.backup.assetId;
+ }
+}