refactor(backups): tag snapshots by backup id and run forget by grouping first by tags

This commit is contained in:
Nicolas Meienberger
2025-11-04 20:09:38 +01:00
parent ecd517341c
commit 01c2a3669c
12 changed files with 66 additions and 268 deletions

View File

@@ -32,7 +32,6 @@ import {
getBackupSchedule,
updateBackupSchedule,
getBackupScheduleForVolume,
upsertBackupSchedule,
runBackupNow,
} from "../sdk.gen";
import { queryOptions, type UseMutationOptions, type DefaultError } from "@tanstack/react-query";
@@ -83,8 +82,6 @@ import type {
UpdateBackupScheduleData,
UpdateBackupScheduleResponse,
GetBackupScheduleForVolumeData,
UpsertBackupScheduleData,
UpsertBackupScheduleResponse,
RunBackupNowData,
RunBackupNowResponse,
} from "../types.gen";
@@ -957,29 +954,6 @@ export const getBackupScheduleForVolumeOptions = (options: Options<GetBackupSche
});
};
/**
* Create or update a backup schedule for a volume
*/
export const upsertBackupScheduleMutation = (
options?: Partial<Options<UpsertBackupScheduleData>>,
): UseMutationOptions<UpsertBackupScheduleResponse, DefaultError, Options<UpsertBackupScheduleData>> => {
const mutationOptions: UseMutationOptions<
UpsertBackupScheduleResponse,
DefaultError,
Options<UpsertBackupScheduleData>
> = {
mutationFn: async (localOptions) => {
const { data } = await upsertBackupSchedule({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
export const runBackupNowQueryKey = (options: Options<RunBackupNowData>) => createQueryKey("runBackupNow", options);
/**

View File

@@ -68,8 +68,6 @@ import type {
UpdateBackupScheduleResponses,
GetBackupScheduleForVolumeData,
GetBackupScheduleForVolumeResponses,
UpsertBackupScheduleData,
UpsertBackupScheduleResponses,
RunBackupNowData,
RunBackupNowResponses,
} from "./types.gen";
@@ -474,22 +472,6 @@ export const getBackupScheduleForVolume = <ThrowOnError extends boolean = false>
});
};
/**
* Create or update a backup schedule for a volume
*/
export const upsertBackupSchedule = <ThrowOnError extends boolean = false>(
options?: Options<UpsertBackupScheduleData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).put<UpsertBackupScheduleResponses, unknown, ThrowOnError>({
url: "/api/v1/backups/upsert",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Trigger a backup immediately for a schedule
*/

View File

@@ -782,7 +782,7 @@ export type ListSnapshotsData = {
name: string;
};
query?: {
volumeId?: string;
backupId?: string;
};
url: "/api/v1/repositories/{name}/snapshots";
};
@@ -1181,11 +1181,11 @@ export type GetBackupScheduleResponse = GetBackupScheduleResponses[keyof GetBack
export type UpdateBackupScheduleData = {
body?: {
cronExpression?: string;
cronExpression: string;
repositoryId: string;
enabled?: boolean;
excludePatterns?: Array<string>;
includePatterns?: Array<string>;
repositoryId?: string;
retentionPolicy?: {
keepDaily?: number;
keepHourly?: number;
@@ -1342,62 +1342,6 @@ export type GetBackupScheduleForVolumeResponses = {
export type GetBackupScheduleForVolumeResponse =
GetBackupScheduleForVolumeResponses[keyof GetBackupScheduleForVolumeResponses];
export type UpsertBackupScheduleData = {
body?: {
cronExpression: string;
enabled: boolean;
repositoryId: string;
volumeId: number;
excludePatterns?: Array<string>;
includePatterns?: Array<string>;
retentionPolicy?: {
keepDaily?: number;
keepHourly?: number;
keepLast?: number;
keepMonthly?: number;
keepWeekly?: number;
keepWithinDuration?: string;
keepYearly?: number;
};
tags?: Array<string>;
};
path?: never;
query?: never;
url: "/api/v1/backups/upsert";
};
export type UpsertBackupScheduleResponses = {
/**
* Backup schedule upserted successfully
*/
200: {
createdAt: number;
cronExpression: string;
enabled: boolean;
excludePatterns: Array<string> | null;
id: number;
includePatterns: Array<string> | null;
lastBackupAt: number | null;
lastBackupError: string | null;
lastBackupStatus: "error" | "success" | null;
nextBackupAt: number | null;
repositoryId: string;
retentionPolicy: {
keepDaily?: number;
keepHourly?: number;
keepLast?: number;
keepMonthly?: number;
keepWeekly?: number;
keepWithinDuration?: string;
keepYearly?: number;
} | null;
updatedAt: number;
volumeId: number;
};
};
export type UpsertBackupScheduleResponse = UpsertBackupScheduleResponses[keyof UpsertBackupScheduleResponses];
export type RunBackupNowData = {
body?: never;
path: {

View File

@@ -6,7 +6,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~
import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/tooltip";
import { formatSnapshotDuration } from "~/modules/repositories/tabs/snapshots";
type Snapshot = ListSnapshotsResponse["snapshots"][0];
type Snapshot = ListSnapshotsResponse[number];
type Props = {
snapshots: Snapshot[];

View File

@@ -4,11 +4,11 @@ import { redirect, useNavigate } from "react-router";
import { toast } from "sonner";
import { Button } from "~/components/ui/button";
import {
upsertBackupScheduleMutation,
getBackupScheduleOptions,
runBackupNowMutation,
deleteBackupScheduleMutation,
listSnapshotsOptions,
updateBackupScheduleMutation,
} from "~/api-client/@tanstack/react-query.gen";
import { parseError } from "~/lib/errors";
import { getCronExpression } from "~/utils/utils";
@@ -26,7 +26,7 @@ export const clientLoader = async ({ params }: Route.LoaderArgs) => {
const snapshots = await listSnapshots({
path: { name: data.repository.name },
query: { volumeId: data.volumeId.toString() },
query: { backupId: params.id },
});
if (snapshots.data) return { snapshots: snapshots.data, schedule: data };
@@ -49,13 +49,13 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
const { data: snapshots } = useQuery({
...listSnapshotsOptions({
path: { name: schedule.repository.name },
query: { volumeId: schedule.volumeId.toString() },
query: { backupId: schedule.id.toString() },
}),
initialData: loaderData.snapshots,
});
const upsertSchedule = useMutation({
...upsertBackupScheduleMutation(),
...updateBackupScheduleMutation(),
onSuccess: () => {
toast.success("Backup schedule saved successfully");
setIsEditMode(false);
@@ -106,8 +106,8 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
if (formValues.keepYearly) retentionPolicy.keepYearly = formValues.keepYearly;
upsertSchedule.mutate({
path: { scheduleId: schedule.id.toString() },
body: {
volumeId: schedule.volumeId,
repositoryId: formValues.repositoryId,
enabled: schedule.enabled,
cronExpression,
@@ -122,8 +122,8 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
if (!schedule) return;
upsertSchedule.mutate({
path: { scheduleId: schedule.id.toString() },
body: {
volumeId: schedule.volumeId,
repositoryId: schedule.repositoryId,
enabled,
cronExpression: schedule.cronExpression,