SPB Git forge

spb/spb-cloud

Public
10commits 1branches 0releases
3.2 MBsize
maindefault branch
1 h agolast push
JavaScript 56.7% TypeScript 42.6%
2.6 KB · 61 lines typescript
Raw Blame History
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