import { Hono } from "hono"; import { validator } from "hono-openapi"; import { createBackupScheduleBody, createBackupScheduleDto, deleteBackupScheduleDto, getBackupScheduleDto, getBackupScheduleForVolumeDto, listBackupSchedulesDto, runBackupNowDto, runForgetDto, stopBackupDto, updateBackupScheduleDto, updateBackupScheduleBody, getScheduleMirrorsDto, updateScheduleMirrorsDto, updateScheduleMirrorsBody, getMirrorCompatibilityDto, reorderBackupSchedulesDto, reorderBackupSchedulesBody, getBackupProgressDto, type CreateBackupScheduleDto, type DeleteBackupScheduleDto, type GetBackupScheduleDto, type GetBackupScheduleForVolumeResponseDto, type ListBackupSchedulesResponseDto, type RunBackupNowDto, type RunForgetDto, type StopBackupDto, type UpdateBackupScheduleDto, type GetScheduleMirrorsDto, type UpdateScheduleMirrorsDto, type GetMirrorCompatibilityDto, type ReorderBackupSchedulesDto, type GetBackupProgressDto, listBackupSchedulesResponse, getBackupScheduleResponse, } from "./backups.dto"; import { backupsService } from "./backups.service"; import { getScheduleNotificationsDto, updateScheduleNotificationsBody, updateScheduleNotificationsDto, type GetScheduleNotificationsDto, type UpdateScheduleNotificationsDto, } from "../notifications/notifications.dto"; import { notificationsService } from "../notifications/notifications.service"; import { requireAuth } from "../auth/auth.middleware"; import { logger } from "@zerobyte/core/node"; import { asShortId } from "~/server/utils/branded"; import { cache, cacheKeys } from "~/server/utils/cache"; import { getScheduleByIdOrShortId } from "./helpers/backup-schedule-lookups"; export const backupScheduleController = new Hono() .use(requireAuth) .get("/", listBackupSchedulesDto, async (c) => { const schedules = await backupsService.listSchedules(); return c.json(listBackupSchedulesResponse.parse(schedules), 200); }) .get("/:shortId", getBackupScheduleDto, async (c) => { const shortId = asShortId(c.req.param("shortId")); const schedule = await getScheduleByIdOrShortId(shortId); return c.json(getBackupScheduleResponse.parse(schedule), 200); }) .get("/volume/:volumeShortId", getBackupScheduleForVolumeDto, async (c) => { const volumeShortId = asShortId(c.req.param("volumeShortId")); const schedule = await backupsService.getScheduleForVolume(volumeShortId); return c.json(schedule, 200); }) .post("/", createBackupScheduleDto, validator("json", createBackupScheduleBody), async (c) => { const body = c.req.valid("json"); const schedule = await backupsService.createSchedule(body); return c.json(schedule, 201); }) .patch("/:shortId", updateBackupScheduleDto, validator("json", updateBackupScheduleBody), async (c) => { const shortId = asShortId(c.req.param("shortId")); const body = c.req.valid("json"); const schedule = await backupsService.updateSchedule(shortId, body); return c.json(schedule, 200); }) .delete("/:shortId", deleteBackupScheduleDto, async (c) => { const shortId = asShortId(c.req.param("shortId")); await backupsService.deleteSchedule(shortId); return c.json({ success: true }, 200); }) .post("/:shortId/run", runBackupNowDto, async (c) => { const shortId = asShortId(c.req.param("shortId")); const schedule = await getScheduleByIdOrShortId(shortId); const result = await backupsService.validateBackupExecution(schedule.id, true); if (result.type === "failure") { throw result.error; } if (result.type === "skipped") { return c.json({ success: true }, 200); } backupsService.executeBackup(schedule.id, true).catch((err) => { logger.error(`Error executing manual backup for schedule ${shortId}:`, err); }); return c.json({ success: true }, 200); }) .post("/:shortId/stop", stopBackupDto, async (c) => { const shortId = asShortId(c.req.param("shortId")); const schedule = await getScheduleByIdOrShortId(shortId); await backupsService.stopBackup(schedule.id); return c.json({ success: true }, 200); }) .post("/:shortId/forget", runForgetDto, async (c) => { const shortId = asShortId(c.req.param("shortId")); const schedule = await getScheduleByIdOrShortId(shortId); await backupsService.runForget(schedule.id); return c.json({ success: true }, 200); }) .get("/:shortId/notifications", getScheduleNotificationsDto, async (c) => { const shortId = asShortId(c.req.param("shortId")); const schedule = await getScheduleByIdOrShortId(shortId); const assignments = await notificationsService.getScheduleNotifications(schedule.id); return c.json(assignments, 200); }) .put( "/:shortId/notifications", updateScheduleNotificationsDto, validator("json", updateScheduleNotificationsBody), async (c) => { const shortId = asShortId(c.req.param("shortId")); const schedule = await getScheduleByIdOrShortId(shortId); const body = c.req.valid("json"); const assignments = await notificationsService.updateScheduleNotifications(schedule.id, body.assignments); return c.json(assignments, 200); }, ) .get("/:shortId/mirrors", getScheduleMirrorsDto, async (c) => { const shortId = asShortId(c.req.param("shortId")); const mirrors = await backupsService.getMirrors(shortId); return c.json(mirrors, 200); }) .put("/:shortId/mirrors", updateScheduleMirrorsDto, validator("json", updateScheduleMirrorsBody), async (c) => { const shortId = asShortId(c.req.param("shortId")); const body = c.req.valid("json"); const mirrors = await backupsService.updateMirrors(shortId, body); return c.json(mirrors, 200); }) .get("/:shortId/mirrors/compatibility", getMirrorCompatibilityDto, async (c) => { const shortId = asShortId(c.req.param("shortId")); const compatibility = await backupsService.getMirrorCompatibility(shortId); return c.json(compatibility, 200); }) .post("/reorder", reorderBackupSchedulesDto, validator("json", reorderBackupSchedulesBody), async (c) => { const body = c.req.valid("json"); await backupsService.reorderSchedules(body.scheduleShortIds.map(asShortId)); return c.json({ success: true }, 200); }) .get("/:shortId/progress", getBackupProgressDto, async (c) => { const shortId = asShortId(c.req.param("shortId")); const schedule = await getScheduleByIdOrShortId(shortId); if (schedule.lastBackupStatus !== "in_progress") { cache.del(cacheKeys.backup.progress(schedule.id)); return c.json(null, 200); } const progress = backupsService.getBackupProgress(schedule.id); return c.json(progress ?? null, 200); });