feat(backend): backup service with retention policy

This commit is contained in:
Nicolas Meienberger
2025-10-25 19:43:36 +02:00
parent 2202ad3247
commit 43e31596f1
16 changed files with 1688 additions and 3 deletions

View File

@@ -23,6 +23,12 @@ import {
deleteRepository,
getRepository,
listSnapshots,
listBackupSchedules,
createBackupSchedule,
deleteBackupSchedule,
getBackupSchedule,
updateBackupSchedule,
runBackupNow,
} from "../sdk.gen";
import { queryOptions, type UseMutationOptions, type DefaultError } from "@tanstack/react-query";
import type {
@@ -59,6 +65,16 @@ import type {
DeleteRepositoryResponse,
GetRepositoryData,
ListSnapshotsData,
ListBackupSchedulesData,
CreateBackupScheduleData,
CreateBackupScheduleResponse,
DeleteBackupScheduleData,
DeleteBackupScheduleResponse,
GetBackupScheduleData,
UpdateBackupScheduleData,
UpdateBackupScheduleResponse,
RunBackupNowData,
RunBackupNowResponse,
} from "../types.gen";
import { client as _heyApiClient } from "../client.gen";
@@ -693,3 +709,174 @@ export const listSnapshotsOptions = (options: Options<ListSnapshotsData>) => {
queryKey: listSnapshotsQueryKey(options),
});
};
export const listBackupSchedulesQueryKey = (options?: Options<ListBackupSchedulesData>) =>
createQueryKey("listBackupSchedules", options);
/**
* List all backup schedules
*/
export const listBackupSchedulesOptions = (options?: Options<ListBackupSchedulesData>) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await listBackupSchedules({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: listBackupSchedulesQueryKey(options),
});
};
export const createBackupScheduleQueryKey = (options?: Options<CreateBackupScheduleData>) =>
createQueryKey("createBackupSchedule", options);
/**
* Create a new backup schedule for a volume
*/
export const createBackupScheduleOptions = (options?: Options<CreateBackupScheduleData>) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await createBackupSchedule({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: createBackupScheduleQueryKey(options),
});
};
/**
* Create a new backup schedule for a volume
*/
export const createBackupScheduleMutation = (
options?: Partial<Options<CreateBackupScheduleData>>,
): UseMutationOptions<CreateBackupScheduleResponse, DefaultError, Options<CreateBackupScheduleData>> => {
const mutationOptions: UseMutationOptions<
CreateBackupScheduleResponse,
DefaultError,
Options<CreateBackupScheduleData>
> = {
mutationFn: async (localOptions) => {
const { data } = await createBackupSchedule({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
/**
* Delete a backup schedule
*/
export const deleteBackupScheduleMutation = (
options?: Partial<Options<DeleteBackupScheduleData>>,
): UseMutationOptions<DeleteBackupScheduleResponse, DefaultError, Options<DeleteBackupScheduleData>> => {
const mutationOptions: UseMutationOptions<
DeleteBackupScheduleResponse,
DefaultError,
Options<DeleteBackupScheduleData>
> = {
mutationFn: async (localOptions) => {
const { data } = await deleteBackupSchedule({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
export const getBackupScheduleQueryKey = (options: Options<GetBackupScheduleData>) =>
createQueryKey("getBackupSchedule", options);
/**
* Get a backup schedule by ID
*/
export const getBackupScheduleOptions = (options: Options<GetBackupScheduleData>) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await getBackupSchedule({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: getBackupScheduleQueryKey(options),
});
};
/**
* Update a backup schedule
*/
export const updateBackupScheduleMutation = (
options?: Partial<Options<UpdateBackupScheduleData>>,
): UseMutationOptions<UpdateBackupScheduleResponse, DefaultError, Options<UpdateBackupScheduleData>> => {
const mutationOptions: UseMutationOptions<
UpdateBackupScheduleResponse,
DefaultError,
Options<UpdateBackupScheduleData>
> = {
mutationFn: async (localOptions) => {
const { data } = await updateBackupSchedule({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
export const runBackupNowQueryKey = (options: Options<RunBackupNowData>) => createQueryKey("runBackupNow", options);
/**
* Trigger a backup immediately for a schedule
*/
export const runBackupNowOptions = (options: Options<RunBackupNowData>) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await runBackupNow({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: runBackupNowQueryKey(options),
});
};
/**
* Trigger a backup immediately for a schedule
*/
export const runBackupNowMutation = (
options?: Partial<Options<RunBackupNowData>>,
): UseMutationOptions<RunBackupNowResponse, DefaultError, Options<RunBackupNowData>> => {
const mutationOptions: UseMutationOptions<RunBackupNowResponse, DefaultError, Options<RunBackupNowData>> = {
mutationFn: async (localOptions) => {
const { data } = await runBackupNow({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};

View File

@@ -54,6 +54,18 @@ import type {
GetRepositoryResponses,
ListSnapshotsData,
ListSnapshotsResponses,
ListBackupSchedulesData,
ListBackupSchedulesResponses,
CreateBackupScheduleData,
CreateBackupScheduleResponses,
DeleteBackupScheduleData,
DeleteBackupScheduleResponses,
GetBackupScheduleData,
GetBackupScheduleResponses,
UpdateBackupScheduleData,
UpdateBackupScheduleResponses,
RunBackupNowData,
RunBackupNowResponses,
} from "./types.gen";
import { client as _heyApiClient } from "./client.gen";
@@ -335,3 +347,83 @@ export const listSnapshots = <ThrowOnError extends boolean = false>(
...options,
});
};
/**
* List all backup schedules
*/
export const listBackupSchedules = <ThrowOnError extends boolean = false>(
options?: Options<ListBackupSchedulesData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).get<ListBackupSchedulesResponses, unknown, ThrowOnError>({
url: "/api/v1/backups",
...options,
});
};
/**
* Create a new backup schedule for a volume
*/
export const createBackupSchedule = <ThrowOnError extends boolean = false>(
options?: Options<CreateBackupScheduleData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<CreateBackupScheduleResponses, unknown, ThrowOnError>({
url: "/api/v1/backups",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Delete a backup schedule
*/
export const deleteBackupSchedule = <ThrowOnError extends boolean = false>(
options: Options<DeleteBackupScheduleData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).delete<DeleteBackupScheduleResponses, unknown, ThrowOnError>({
url: "/api/v1/backups/{scheduleId}",
...options,
});
};
/**
* Get a backup schedule by ID
*/
export const getBackupSchedule = <ThrowOnError extends boolean = false>(
options: Options<GetBackupScheduleData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).get<GetBackupScheduleResponses, unknown, ThrowOnError>({
url: "/api/v1/backups/{scheduleId}",
...options,
});
};
/**
* Update a backup schedule
*/
export const updateBackupSchedule = <ThrowOnError extends boolean = false>(
options: Options<UpdateBackupScheduleData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).patch<UpdateBackupScheduleResponses, unknown, ThrowOnError>({
url: "/api/v1/backups/{scheduleId}",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Trigger a backup immediately for a schedule
*/
export const runBackupNow = <ThrowOnError extends boolean = false>(
options: Options<RunBackupNowData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).post<RunBackupNowResponses, unknown, ThrowOnError>({
url: "/api/v1/backups/{scheduleId}/run",
...options,
});
};

View File

@@ -804,6 +804,258 @@ export type ListSnapshotsResponses = {
export type ListSnapshotsResponse = ListSnapshotsResponses[keyof ListSnapshotsResponses];
export type ListBackupSchedulesData = {
body?: never;
path?: never;
query?: never;
url: "/api/v1/backups";
};
export type ListBackupSchedulesResponses = {
/**
* List of backup schedules
*/
200: {
schedules: Array<{
createdAt: number;
cronExpression: string;
enabled: boolean;
excludePatterns: Array<string>;
id: number;
includePatterns: Array<string>;
lastBackupAt: number | null;
lastBackupError: string | null;
lastBackupStatus: "error" | "success" | null;
nextBackupAt: number | null;
repositoryId: string;
repositoryName: string;
retentionPolicy: {
keepDaily?: number;
keepHourly?: number;
keepLast?: number;
keepMonthly?: number;
keepWeekly?: number;
keepWithinDuration?: string;
keepYearly?: number;
} | null;
updatedAt: number;
volumeId: number;
volumeName: string;
}>;
};
};
export type ListBackupSchedulesResponse = ListBackupSchedulesResponses[keyof ListBackupSchedulesResponses];
export type CreateBackupScheduleData = {
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";
};
export type CreateBackupScheduleResponses = {
/**
* Backup schedule created successfully
*/
201: {
message: string;
schedule: {
createdAt: number;
cronExpression: string;
enabled: boolean;
excludePatterns: Array<string>;
id: number;
includePatterns: Array<string>;
lastBackupAt: number | null;
lastBackupError: string | null;
lastBackupStatus: "error" | "success" | null;
nextBackupAt: number | null;
repositoryId: string;
repositoryName: string;
retentionPolicy: {
keepDaily?: number;
keepHourly?: number;
keepLast?: number;
keepMonthly?: number;
keepWeekly?: number;
keepWithinDuration?: string;
keepYearly?: number;
} | null;
updatedAt: number;
volumeId: number;
volumeName: string;
};
};
};
export type CreateBackupScheduleResponse = CreateBackupScheduleResponses[keyof CreateBackupScheduleResponses];
export type DeleteBackupScheduleData = {
body?: never;
path: {
scheduleId: string;
};
query?: never;
url: "/api/v1/backups/{scheduleId}";
};
export type DeleteBackupScheduleResponses = {
/**
* Backup schedule deleted successfully
*/
200: {
message: string;
};
};
export type DeleteBackupScheduleResponse = DeleteBackupScheduleResponses[keyof DeleteBackupScheduleResponses];
export type GetBackupScheduleData = {
body?: never;
path: {
scheduleId: string;
};
query?: never;
url: "/api/v1/backups/{scheduleId}";
};
export type GetBackupScheduleResponses = {
/**
* Backup schedule details
*/
200: {
schedule: {
createdAt: number;
cronExpression: string;
enabled: boolean;
excludePatterns: Array<string>;
id: number;
includePatterns: Array<string>;
lastBackupAt: number | null;
lastBackupError: string | null;
lastBackupStatus: "error" | "success" | null;
nextBackupAt: number | null;
repositoryId: string;
repositoryName: string;
retentionPolicy: {
keepDaily?: number;
keepHourly?: number;
keepLast?: number;
keepMonthly?: number;
keepWeekly?: number;
keepWithinDuration?: string;
keepYearly?: number;
} | null;
updatedAt: number;
volumeId: number;
volumeName: string;
};
};
};
export type GetBackupScheduleResponse = GetBackupScheduleResponses[keyof GetBackupScheduleResponses];
export type UpdateBackupScheduleData = {
body?: {
cronExpression?: string;
enabled?: boolean;
excludePatterns?: Array<string>;
includePatterns?: Array<string>;
repositoryId?: string;
retentionPolicy?: {
keepDaily?: number;
keepHourly?: number;
keepLast?: number;
keepMonthly?: number;
keepWeekly?: number;
keepWithinDuration?: string;
keepYearly?: number;
};
tags?: Array<string>;
};
path: {
scheduleId: string;
};
query?: never;
url: "/api/v1/backups/{scheduleId}";
};
export type UpdateBackupScheduleResponses = {
/**
* Backup schedule updated successfully
*/
200: {
message: string;
schedule: {
createdAt: number;
cronExpression: string;
enabled: boolean;
excludePatterns: Array<string>;
id: number;
includePatterns: Array<string>;
lastBackupAt: number | null;
lastBackupError: string | null;
lastBackupStatus: "error" | "success" | null;
nextBackupAt: number | null;
repositoryId: string;
repositoryName: string;
retentionPolicy: {
keepDaily?: number;
keepHourly?: number;
keepLast?: number;
keepMonthly?: number;
keepWeekly?: number;
keepWithinDuration?: string;
keepYearly?: number;
} | null;
updatedAt: number;
volumeId: number;
volumeName: string;
};
};
};
export type UpdateBackupScheduleResponse = UpdateBackupScheduleResponses[keyof UpdateBackupScheduleResponses];
export type RunBackupNowData = {
body?: never;
path: {
scheduleId: string;
};
query?: never;
url: "/api/v1/backups/{scheduleId}/run";
};
export type RunBackupNowResponses = {
/**
* Backup started successfully
*/
200: {
backupStarted: boolean;
message: string;
};
};
export type RunBackupNowResponse = RunBackupNowResponses[keyof RunBackupNowResponses];
export type ClientOptions = {
baseUrl: "http://192.168.2.42:4096" | (string & {});
};