JavaScript 56.7%
TypeScript 42.6%
1import { existsSync, mkdirSync, createWriteStream, readdirSync, statSync } from "fs";2import { rm } from "fs/promises";3import path from "path";4import archiver from "archiver";5import { prisma } from "./prisma";67const BACKUP_DIR = process.env.BACKUP_DIR || "./backups";8const UPLOAD_DIR = process.env.UPLOAD_DIR || "./uploads";910if (!existsSync(BACKUP_DIR)) mkdirSync(BACKUP_DIR, { recursive: true });1112export async function createBackup(type: "full" | "incremental" = "full"): Promise<{ id: string; path: string; size: number }> {13 const log = await prisma.backupLog.create({ data: { status: "running", type } });1415 try {16 const timestamp = new Date().toISOString().replace(/[:.]/g, "-");17 const backupPath = path.join(BACKUP_DIR, `backup-${type}-${timestamp}.zip`);18 const output = createWriteStream(backupPath);19 const archive = archiver("zip", { zlib: { level: 9 } });2021 const completed = new Promise<number>((resolve, reject) => {22 output.on("close", () => resolve(archive.pointer()));23 archive.on("error", reject);24 });2526 archive.pipe(output);27 if (existsSync(UPLOAD_DIR)) archive.directory(UPLOAD_DIR, "uploads");28 const dbPath = path.resolve("./prisma/dev.db");29 if (existsSync(dbPath)) archive.file(dbPath, { name: "database/dev.db" });30 await archive.finalize();31 const size = await completed;3233 await prisma.backupLog.update({ where: { id: log.id }, data: { status: "completed", path: backupPath, size: BigInt(size), completedAt: new Date() } });34 return { id: log.id, path: backupPath, size };35 } catch (error) {36 await prisma.backupLog.update({ where: { id: log.id }, data: { status: "failed", error: (error as Error).message, completedAt: new Date() } });37 throw error;38 }39}4041export async function listBackups() {42 const logs = await prisma.backupLog.findMany({ orderBy: { startedAt: "desc" }, take: 50 });43 return logs.map((l) => ({ id: l.id, status: l.status, type: l.type, size: l.size ? Number(l.size) : 0, path: l.path, startedAt: l.startedAt, completedAt: l.completedAt }));44}4546export async function deleteBackup(id: string): Promise<boolean> {47 const log = await prisma.backupLog.findUnique({ where: { id } });48 if (!log) return false;49 if (log.path && existsSync(log.path)) await rm(log.path);50 await prisma.backupLog.delete({ where: { id } });51 return true;52}5354export function getBackupStats() {55 if (!existsSync(BACKUP_DIR)) return { count: 0, totalSize: 0 };56 const files = readdirSync(BACKUP_DIR);57 let totalSize = 0;58 for (const file of files) totalSize += statSync(path.join(BACKUP_DIR, file)).size;59 return { count: files.length, totalSize };60}61