refactor: simplify dtos and improve type saftey in json returns

This commit is contained in:
Nicolas Meienberger
2025-10-29 18:28:00 +01:00
parent d1c1adaba7
commit b188a84af3
26 changed files with 667 additions and 751 deletions

View File

@@ -10,6 +10,13 @@ import {
runBackupNowDto,
updateBackupScheduleBody,
updateBackupScheduleDto,
type CreateBackupScheduleDto,
type DeleteBackupScheduleDto,
type GetBackupScheduleDto,
type GetBackupScheduleForVolumeResponseDto,
type ListBackupSchedulesResponseDto,
type RunBackupNowDto,
type UpdateBackupScheduleDto,
} from "./backups.dto";
import { backupsService } from "./backups.service";
@@ -17,27 +24,27 @@ export const backupScheduleController = new Hono()
.get("/", listBackupSchedulesDto, async (c) => {
const schedules = await backupsService.listSchedules();
return c.json({ schedules }, 200);
return c.json<ListBackupSchedulesResponseDto>(schedules, 200);
})
.get("/:scheduleId", getBackupScheduleDto, async (c) => {
const scheduleId = c.req.param("scheduleId");
const schedule = await backupsService.getSchedule(Number(scheduleId));
return c.json({ schedule }, 200);
return c.json<GetBackupScheduleDto>(schedule, 200);
})
.get("/volume/:volumeId", getBackupScheduleForVolumeDto, async (c) => {
const volumeId = c.req.param("volumeId");
const schedule = await backupsService.getScheduleForVolume(Number(volumeId));
return c.json(schedule, 200);
return c.json<GetBackupScheduleForVolumeResponseDto>(schedule, 200);
})
.post("/", createBackupScheduleDto, validator("json", createBackupScheduleBody), async (c) => {
const body = c.req.valid("json");
const schedule = await backupsService.createSchedule(body);
return c.json({ message: "Backup schedule created successfully", schedule }, 201);
return c.json<CreateBackupScheduleDto>(schedule, 201);
})
.patch("/:scheduleId", updateBackupScheduleDto, validator("json", updateBackupScheduleBody), async (c) => {
const scheduleId = c.req.param("scheduleId");
@@ -45,14 +52,14 @@ export const backupScheduleController = new Hono()
const schedule = await backupsService.updateSchedule(Number(scheduleId), body);
return c.json({ message: "Backup schedule updated successfully", schedule }, 200);
return c.json<UpdateBackupScheduleDto>(schedule, 200);
})
.delete("/:scheduleId", deleteBackupScheduleDto, async (c) => {
const scheduleId = c.req.param("scheduleId");
await backupsService.deleteSchedule(Number(scheduleId));
return c.json({ message: "Backup schedule deleted successfully" }, 200);
return c.json<DeleteBackupScheduleDto>({ success: true }, 200);
})
.post("/:scheduleId/run", runBackupNowDto, async (c) => {
const scheduleId = c.req.param("scheduleId");
@@ -61,11 +68,5 @@ export const backupScheduleController = new Hono()
console.error("Backup execution failed:", error);
});
return c.json(
{
message: "Backup started",
backupStarted: true,
},
200,
);
return c.json<RunBackupNowDto>({ success: true }, 200);
});

View File

@@ -35,9 +35,7 @@ export type BackupScheduleDto = typeof backupScheduleSchema.infer;
/**
* List all backup schedules
*/
export const listBackupSchedulesResponse = type({
schedules: backupScheduleSchema.array(),
});
export const listBackupSchedulesResponse = backupScheduleSchema.array();
export type ListBackupSchedulesResponseDto = typeof listBackupSchedulesResponse.infer;
@@ -60,9 +58,7 @@ export const listBackupSchedulesDto = describeRoute({
/**
* Get a single backup schedule
*/
export const getBackupScheduleResponse = type({
schedule: backupScheduleSchema,
});
export const getBackupScheduleResponse = backupScheduleSchema;
export type GetBackupScheduleDto = typeof getBackupScheduleResponse.infer;
@@ -118,10 +114,7 @@ export const createBackupScheduleBody = type({
export type CreateBackupScheduleBody = typeof createBackupScheduleBody.infer;
export const createBackupScheduleResponse = type({
message: "string",
schedule: backupScheduleSchema,
});
export const createBackupScheduleResponse = backupScheduleSchema;
export type CreateBackupScheduleDto = typeof createBackupScheduleResponse.infer;
@@ -156,10 +149,9 @@ export const updateBackupScheduleBody = type({
export type UpdateBackupScheduleBody = typeof updateBackupScheduleBody.infer;
export const updateBackupScheduleResponse = type({
message: "string",
schedule: backupScheduleSchema,
});
export const updateBackupScheduleResponse = backupScheduleSchema;
export type UpdateBackupScheduleDto = typeof updateBackupScheduleResponse.infer;
export const updateBackupScheduleDto = describeRoute({
description: "Update a backup schedule",
@@ -181,9 +173,11 @@ export const updateBackupScheduleDto = describeRoute({
* Delete a backup schedule
*/
export const deleteBackupScheduleResponse = type({
message: "string",
success: "boolean",
});
export type DeleteBackupScheduleDto = typeof deleteBackupScheduleResponse.infer;
export const deleteBackupScheduleDto = describeRoute({
description: "Delete a backup schedule",
operationId: "deleteBackupSchedule",
@@ -204,10 +198,11 @@ export const deleteBackupScheduleDto = describeRoute({
* Run a backup immediately
*/
export const runBackupNowResponse = type({
message: "string",
backupStarted: "boolean",
success: "boolean",
});
export type RunBackupNowDto = typeof runBackupNowResponse.infer;
export const runBackupNowDto = describeRoute({
description: "Trigger a backup immediately for a schedule",
operationId: "runBackupNow",

View File

@@ -10,19 +10,19 @@ import { getVolumePath } from "../volumes/helpers";
import type { CreateBackupScheduleBody, UpdateBackupScheduleBody } from "./backups.dto";
import { toMessage } from "../../utils/errors";
const calculateNextRun = (cronExpression: string): Date => {
const calculateNextRun = (cronExpression: string): number => {
try {
const interval = CronExpressionParser.parse(cronExpression, {
currentDate: new Date(),
tz: "UTC",
});
return interval.next().toDate();
return interval.next().getTime();
} catch (error) {
logger.error(`Failed to parse cron expression "${cronExpression}": ${error}`);
const fallback = new Date();
fallback.setMinutes(fallback.getMinutes() + 1);
return fallback;
return fallback.getTime();
}
};
@@ -123,7 +123,7 @@ const updateSchedule = async (scheduleId: number, data: UpdateBackupScheduleBody
const [updated] = await db
.update(backupSchedulesTable)
.set({ ...data, nextBackupAt, updatedAt: new Date() })
.set({ ...data, nextBackupAt, updatedAt: Date.now() })
.where(eq(backupSchedulesTable.id, scheduleId))
.returning();
@@ -204,11 +204,11 @@ const executeBackup = async (scheduleId: number) => {
await db
.update(backupSchedulesTable)
.set({
lastBackupAt: new Date(),
lastBackupAt: Date.now(),
lastBackupStatus: "success",
lastBackupError: null,
nextBackupAt: nextBackupAt,
updatedAt: new Date(),
updatedAt: Date.now(),
})
.where(eq(backupSchedulesTable.id, scheduleId));
@@ -219,10 +219,10 @@ const executeBackup = async (scheduleId: number) => {
await db
.update(backupSchedulesTable)
.set({
lastBackupAt: new Date(),
lastBackupAt: Date.now(),
lastBackupStatus: "error",
lastBackupError: toMessage(error),
updatedAt: new Date(),
updatedAt: Date.now(),
})
.where(eq(backupSchedulesTable.id, scheduleId));
@@ -231,7 +231,7 @@ const executeBackup = async (scheduleId: number) => {
};
const getSchedulesToExecute = async () => {
const now = new Date();
const now = Date.now();
const schedules = await db.query.backupSchedulesTable.findMany({
where: eq(backupSchedulesTable.enabled, true),
});