refactor: always use short id in api calls (#545)

This commit is contained in:
Nico 2026-02-19 20:08:40 +01:00 committed by GitHub
parent f3608b0f30
commit 8681ebc0c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 562 additions and 495 deletions

View file

@ -1288,7 +1288,7 @@ export const getMirrorCompatibilityOptions = (options: Options<GetMirrorCompatib
});
/**
* Reorder backup schedules by providing an array of schedule IDs in the desired order
* Reorder backup schedules by providing an array of schedule short IDs in the desired order
*/
export const reorderBackupSchedulesMutation = (
options?: Partial<Options<ReorderBackupSchedulesData>>,

View file

@ -210,7 +210,7 @@ export const testConnection = <ThrowOnError extends boolean = false>(
*/
export const deleteVolume = <ThrowOnError extends boolean = false>(options: Options<DeleteVolumeData, ThrowOnError>) =>
(options.client ?? client).delete<DeleteVolumeResponses, unknown, ThrowOnError>({
url: "/api/v1/volumes/{id}",
url: "/api/v1/volumes/{shortId}",
...options,
});
@ -219,7 +219,7 @@ export const deleteVolume = <ThrowOnError extends boolean = false>(options: Opti
*/
export const getVolume = <ThrowOnError extends boolean = false>(options: Options<GetVolumeData, ThrowOnError>) =>
(options.client ?? client).get<GetVolumeResponses, GetVolumeErrors, ThrowOnError>({
url: "/api/v1/volumes/{id}",
url: "/api/v1/volumes/{shortId}",
...options,
});
@ -228,7 +228,7 @@ export const getVolume = <ThrowOnError extends boolean = false>(options: Options
*/
export const updateVolume = <ThrowOnError extends boolean = false>(options: Options<UpdateVolumeData, ThrowOnError>) =>
(options.client ?? client).put<UpdateVolumeResponses, UpdateVolumeErrors, ThrowOnError>({
url: "/api/v1/volumes/{id}",
url: "/api/v1/volumes/{shortId}",
...options,
headers: {
"Content-Type": "application/json",
@ -241,7 +241,7 @@ export const updateVolume = <ThrowOnError extends boolean = false>(options: Opti
*/
export const mountVolume = <ThrowOnError extends boolean = false>(options: Options<MountVolumeData, ThrowOnError>) =>
(options.client ?? client).post<MountVolumeResponses, unknown, ThrowOnError>({
url: "/api/v1/volumes/{id}/mount",
url: "/api/v1/volumes/{shortId}/mount",
...options,
});
@ -252,7 +252,7 @@ export const unmountVolume = <ThrowOnError extends boolean = false>(
options: Options<UnmountVolumeData, ThrowOnError>,
) =>
(options.client ?? client).post<UnmountVolumeResponses, unknown, ThrowOnError>({
url: "/api/v1/volumes/{id}/unmount",
url: "/api/v1/volumes/{shortId}/unmount",
...options,
});
@ -263,7 +263,7 @@ export const healthCheckVolume = <ThrowOnError extends boolean = false>(
options: Options<HealthCheckVolumeData, ThrowOnError>,
) =>
(options.client ?? client).post<HealthCheckVolumeResponses, HealthCheckVolumeErrors, ThrowOnError>({
url: "/api/v1/volumes/{id}/health-check",
url: "/api/v1/volumes/{shortId}/health-check",
...options,
});
@ -272,7 +272,7 @@ export const healthCheckVolume = <ThrowOnError extends boolean = false>(
*/
export const listFiles = <ThrowOnError extends boolean = false>(options: Options<ListFilesData, ThrowOnError>) =>
(options.client ?? client).get<ListFilesResponses, unknown, ThrowOnError>({
url: "/api/v1/volumes/{id}/files",
url: "/api/v1/volumes/{shortId}/files",
...options,
});
@ -331,7 +331,7 @@ export const deleteRepository = <ThrowOnError extends boolean = false>(
options: Options<DeleteRepositoryData, ThrowOnError>,
) =>
(options.client ?? client).delete<DeleteRepositoryResponses, unknown, ThrowOnError>({
url: "/api/v1/repositories/{id}",
url: "/api/v1/repositories/{shortId}",
...options,
});
@ -342,7 +342,7 @@ export const getRepository = <ThrowOnError extends boolean = false>(
options: Options<GetRepositoryData, ThrowOnError>,
) =>
(options.client ?? client).get<GetRepositoryResponses, unknown, ThrowOnError>({
url: "/api/v1/repositories/{id}",
url: "/api/v1/repositories/{shortId}",
...options,
});
@ -353,7 +353,7 @@ export const updateRepository = <ThrowOnError extends boolean = false>(
options: Options<UpdateRepositoryData, ThrowOnError>,
) =>
(options.client ?? client).patch<UpdateRepositoryResponses, UpdateRepositoryErrors, ThrowOnError>({
url: "/api/v1/repositories/{id}",
url: "/api/v1/repositories/{shortId}",
...options,
headers: {
"Content-Type": "application/json",
@ -368,7 +368,7 @@ export const deleteSnapshots = <ThrowOnError extends boolean = false>(
options: Options<DeleteSnapshotsData, ThrowOnError>,
) =>
(options.client ?? client).delete<DeleteSnapshotsResponses, unknown, ThrowOnError>({
url: "/api/v1/repositories/{id}/snapshots",
url: "/api/v1/repositories/{shortId}/snapshots",
...options,
headers: {
"Content-Type": "application/json",
@ -383,7 +383,7 @@ export const listSnapshots = <ThrowOnError extends boolean = false>(
options: Options<ListSnapshotsData, ThrowOnError>,
) =>
(options.client ?? client).get<ListSnapshotsResponses, unknown, ThrowOnError>({
url: "/api/v1/repositories/{id}/snapshots",
url: "/api/v1/repositories/{shortId}/snapshots",
...options,
});
@ -394,7 +394,7 @@ export const refreshSnapshots = <ThrowOnError extends boolean = false>(
options: Options<RefreshSnapshotsData, ThrowOnError>,
) =>
(options.client ?? client).post<RefreshSnapshotsResponses, unknown, ThrowOnError>({
url: "/api/v1/repositories/{id}/snapshots/refresh",
url: "/api/v1/repositories/{shortId}/snapshots/refresh",
...options,
});
@ -405,7 +405,7 @@ export const deleteSnapshot = <ThrowOnError extends boolean = false>(
options: Options<DeleteSnapshotData, ThrowOnError>,
) =>
(options.client ?? client).delete<DeleteSnapshotResponses, unknown, ThrowOnError>({
url: "/api/v1/repositories/{id}/snapshots/{snapshotId}",
url: "/api/v1/repositories/{shortId}/snapshots/{snapshotId}",
...options,
});
@ -416,7 +416,7 @@ export const getSnapshotDetails = <ThrowOnError extends boolean = false>(
options: Options<GetSnapshotDetailsData, ThrowOnError>,
) =>
(options.client ?? client).get<GetSnapshotDetailsResponses, unknown, ThrowOnError>({
url: "/api/v1/repositories/{id}/snapshots/{snapshotId}",
url: "/api/v1/repositories/{shortId}/snapshots/{snapshotId}",
...options,
});
@ -427,7 +427,7 @@ export const listSnapshotFiles = <ThrowOnError extends boolean = false>(
options: Options<ListSnapshotFilesData, ThrowOnError>,
) =>
(options.client ?? client).get<ListSnapshotFilesResponses, unknown, ThrowOnError>({
url: "/api/v1/repositories/{id}/snapshots/{snapshotId}/files",
url: "/api/v1/repositories/{shortId}/snapshots/{snapshotId}/files",
...options,
});
@ -438,7 +438,7 @@ export const restoreSnapshot = <ThrowOnError extends boolean = false>(
options: Options<RestoreSnapshotData, ThrowOnError>,
) =>
(options.client ?? client).post<RestoreSnapshotResponses, unknown, ThrowOnError>({
url: "/api/v1/repositories/{id}/restore",
url: "/api/v1/repositories/{shortId}/restore",
...options,
headers: {
"Content-Type": "application/json",
@ -451,7 +451,7 @@ export const restoreSnapshot = <ThrowOnError extends boolean = false>(
*/
export const cancelDoctor = <ThrowOnError extends boolean = false>(options: Options<CancelDoctorData, ThrowOnError>) =>
(options.client ?? client).delete<CancelDoctorResponses, CancelDoctorErrors, ThrowOnError>({
url: "/api/v1/repositories/{id}/doctor",
url: "/api/v1/repositories/{shortId}/doctor",
...options,
});
@ -460,7 +460,7 @@ export const cancelDoctor = <ThrowOnError extends boolean = false>(options: Opti
*/
export const startDoctor = <ThrowOnError extends boolean = false>(options: Options<StartDoctorData, ThrowOnError>) =>
(options.client ?? client).post<StartDoctorResponses, StartDoctorErrors, ThrowOnError>({
url: "/api/v1/repositories/{id}/doctor",
url: "/api/v1/repositories/{shortId}/doctor",
...options,
});
@ -471,7 +471,7 @@ export const unlockRepository = <ThrowOnError extends boolean = false>(
options: Options<UnlockRepositoryData, ThrowOnError>,
) =>
(options.client ?? client).post<UnlockRepositoryResponses, unknown, ThrowOnError>({
url: "/api/v1/repositories/{id}/unlock",
url: "/api/v1/repositories/{shortId}/unlock",
...options,
});
@ -480,7 +480,7 @@ export const unlockRepository = <ThrowOnError extends boolean = false>(
*/
export const tagSnapshots = <ThrowOnError extends boolean = false>(options: Options<TagSnapshotsData, ThrowOnError>) =>
(options.client ?? client).post<TagSnapshotsResponses, unknown, ThrowOnError>({
url: "/api/v1/repositories/{id}/snapshots/tag",
url: "/api/v1/repositories/{shortId}/snapshots/tag",
...options,
headers: {
"Content-Type": "application/json",
@ -493,7 +493,7 @@ export const tagSnapshots = <ThrowOnError extends boolean = false>(options: Opti
*/
export const devPanelExec = <ThrowOnError extends boolean = false>(options: Options<DevPanelExecData, ThrowOnError>) =>
(options.client ?? client).sse.post<DevPanelExecResponses, DevPanelExecErrors, ThrowOnError>({
url: "/api/v1/repositories/{id}/exec",
url: "/api/v1/repositories/{shortId}/exec",
...options,
headers: {
"Content-Type": "application/json",
@ -534,7 +534,7 @@ export const deleteBackupSchedule = <ThrowOnError extends boolean = false>(
options: Options<DeleteBackupScheduleData, ThrowOnError>,
) =>
(options.client ?? client).delete<DeleteBackupScheduleResponses, unknown, ThrowOnError>({
url: "/api/v1/backups/{scheduleId}",
url: "/api/v1/backups/{shortId}",
...options,
});
@ -545,7 +545,7 @@ export const getBackupSchedule = <ThrowOnError extends boolean = false>(
options: Options<GetBackupScheduleData, ThrowOnError>,
) =>
(options.client ?? client).get<GetBackupScheduleResponses, unknown, ThrowOnError>({
url: "/api/v1/backups/{scheduleId}",
url: "/api/v1/backups/{shortId}",
...options,
});
@ -556,7 +556,7 @@ export const updateBackupSchedule = <ThrowOnError extends boolean = false>(
options: Options<UpdateBackupScheduleData, ThrowOnError>,
) =>
(options.client ?? client).patch<UpdateBackupScheduleResponses, unknown, ThrowOnError>({
url: "/api/v1/backups/{scheduleId}",
url: "/api/v1/backups/{shortId}",
...options,
headers: {
"Content-Type": "application/json",
@ -571,7 +571,7 @@ export const getBackupScheduleForVolume = <ThrowOnError extends boolean = false>
options: Options<GetBackupScheduleForVolumeData, ThrowOnError>,
) =>
(options.client ?? client).get<GetBackupScheduleForVolumeResponses, unknown, ThrowOnError>({
url: "/api/v1/backups/volume/{volumeId}",
url: "/api/v1/backups/volume/{volumeShortId}",
...options,
});
@ -580,7 +580,7 @@ export const getBackupScheduleForVolume = <ThrowOnError extends boolean = false>
*/
export const runBackupNow = <ThrowOnError extends boolean = false>(options: Options<RunBackupNowData, ThrowOnError>) =>
(options.client ?? client).post<RunBackupNowResponses, unknown, ThrowOnError>({
url: "/api/v1/backups/{scheduleId}/run",
url: "/api/v1/backups/{shortId}/run",
...options,
});
@ -589,7 +589,7 @@ export const runBackupNow = <ThrowOnError extends boolean = false>(options: Opti
*/
export const stopBackup = <ThrowOnError extends boolean = false>(options: Options<StopBackupData, ThrowOnError>) =>
(options.client ?? client).post<StopBackupResponses, StopBackupErrors, ThrowOnError>({
url: "/api/v1/backups/{scheduleId}/stop",
url: "/api/v1/backups/{shortId}/stop",
...options,
});
@ -598,7 +598,7 @@ export const stopBackup = <ThrowOnError extends boolean = false>(options: Option
*/
export const runForget = <ThrowOnError extends boolean = false>(options: Options<RunForgetData, ThrowOnError>) =>
(options.client ?? client).post<RunForgetResponses, unknown, ThrowOnError>({
url: "/api/v1/backups/{scheduleId}/forget",
url: "/api/v1/backups/{shortId}/forget",
...options,
});
@ -609,7 +609,7 @@ export const getScheduleNotifications = <ThrowOnError extends boolean = false>(
options: Options<GetScheduleNotificationsData, ThrowOnError>,
) =>
(options.client ?? client).get<GetScheduleNotificationsResponses, unknown, ThrowOnError>({
url: "/api/v1/backups/{scheduleId}/notifications",
url: "/api/v1/backups/{shortId}/notifications",
...options,
});
@ -620,7 +620,7 @@ export const updateScheduleNotifications = <ThrowOnError extends boolean = false
options: Options<UpdateScheduleNotificationsData, ThrowOnError>,
) =>
(options.client ?? client).put<UpdateScheduleNotificationsResponses, unknown, ThrowOnError>({
url: "/api/v1/backups/{scheduleId}/notifications",
url: "/api/v1/backups/{shortId}/notifications",
...options,
headers: {
"Content-Type": "application/json",
@ -635,7 +635,7 @@ export const getScheduleMirrors = <ThrowOnError extends boolean = false>(
options: Options<GetScheduleMirrorsData, ThrowOnError>,
) =>
(options.client ?? client).get<GetScheduleMirrorsResponses, unknown, ThrowOnError>({
url: "/api/v1/backups/{scheduleId}/mirrors",
url: "/api/v1/backups/{shortId}/mirrors",
...options,
});
@ -646,7 +646,7 @@ export const updateScheduleMirrors = <ThrowOnError extends boolean = false>(
options: Options<UpdateScheduleMirrorsData, ThrowOnError>,
) =>
(options.client ?? client).put<UpdateScheduleMirrorsResponses, unknown, ThrowOnError>({
url: "/api/v1/backups/{scheduleId}/mirrors",
url: "/api/v1/backups/{shortId}/mirrors",
...options,
headers: {
"Content-Type": "application/json",
@ -661,12 +661,12 @@ export const getMirrorCompatibility = <ThrowOnError extends boolean = false>(
options: Options<GetMirrorCompatibilityData, ThrowOnError>,
) =>
(options.client ?? client).get<GetMirrorCompatibilityResponses, unknown, ThrowOnError>({
url: "/api/v1/backups/{scheduleId}/mirrors/compatibility",
url: "/api/v1/backups/{shortId}/mirrors/compatibility",
...options,
});
/**
* Reorder backup schedules by providing an array of schedule IDs in the desired order
* Reorder backup schedules by providing an array of schedule short IDs in the desired order
*/
export const reorderBackupSchedules = <ThrowOnError extends boolean = false>(
options?: Options<ReorderBackupSchedulesData, ThrowOnError>,

View file

@ -346,10 +346,10 @@ export type TestConnectionResponse = TestConnectionResponses[keyof TestConnectio
export type DeleteVolumeData = {
body?: never;
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/volumes/{id}";
url: "/api/v1/volumes/{shortId}";
};
export type DeleteVolumeResponses = {
@ -366,10 +366,10 @@ export type DeleteVolumeResponse = DeleteVolumeResponses[keyof DeleteVolumeRespo
export type GetVolumeData = {
body?: never;
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/volumes/{id}";
url: "/api/v1/volumes/{shortId}";
};
export type GetVolumeErrors = {
@ -520,10 +520,10 @@ export type UpdateVolumeData = {
name?: string;
};
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/volumes/{id}";
url: "/api/v1/volumes/{shortId}";
};
export type UpdateVolumeErrors = {
@ -610,10 +610,10 @@ export type UpdateVolumeResponse = UpdateVolumeResponses[keyof UpdateVolumeRespo
export type MountVolumeData = {
body?: never;
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/volumes/{id}/mount";
url: "/api/v1/volumes/{shortId}/mount";
};
export type MountVolumeResponses = {
@ -631,10 +631,10 @@ export type MountVolumeResponse = MountVolumeResponses[keyof MountVolumeResponse
export type UnmountVolumeData = {
body?: never;
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/volumes/{id}/unmount";
url: "/api/v1/volumes/{shortId}/unmount";
};
export type UnmountVolumeResponses = {
@ -652,10 +652,10 @@ export type UnmountVolumeResponse = UnmountVolumeResponses[keyof UnmountVolumeRe
export type HealthCheckVolumeData = {
body?: never;
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/volumes/{id}/health-check";
url: "/api/v1/volumes/{shortId}/health-check";
};
export type HealthCheckVolumeErrors = {
@ -680,14 +680,14 @@ export type HealthCheckVolumeResponse = HealthCheckVolumeResponses[keyof HealthC
export type ListFilesData = {
body?: never;
path: {
id: string;
shortId: string;
};
query?: {
limit?: string;
offset?: string;
path?: string;
};
url: "/api/v1/volumes/{id}/files";
url: "/api/v1/volumes/{shortId}/files";
};
export type ListFilesResponses = {
@ -1159,10 +1159,10 @@ export type ListRcloneRemotesResponse = ListRcloneRemotesResponses[keyof ListRcl
export type DeleteRepositoryData = {
body?: never;
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/repositories/{id}";
url: "/api/v1/repositories/{shortId}";
};
export type DeleteRepositoryResponses = {
@ -1179,10 +1179,10 @@ export type DeleteRepositoryResponse = DeleteRepositoryResponses[keyof DeleteRep
export type GetRepositoryData = {
body?: never;
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/repositories/{id}";
url: "/api/v1/repositories/{shortId}";
};
export type GetRepositoryResponses = {
@ -1553,10 +1553,10 @@ export type UpdateRepositoryData = {
name?: string;
};
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/repositories/{id}";
url: "/api/v1/repositories/{shortId}";
};
export type UpdateRepositoryErrors = {
@ -1775,10 +1775,10 @@ export type DeleteSnapshotsData = {
snapshotIds: Array<string>;
};
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/repositories/{id}/snapshots";
url: "/api/v1/repositories/{shortId}/snapshots";
};
export type DeleteSnapshotsResponses = {
@ -1795,12 +1795,12 @@ export type DeleteSnapshotsResponse = DeleteSnapshotsResponses[keyof DeleteSnaps
export type ListSnapshotsData = {
body?: never;
path: {
id: string;
shortId: string;
};
query?: {
backupId?: string;
};
url: "/api/v1/repositories/{id}/snapshots";
url: "/api/v1/repositories/{shortId}/snapshots";
};
export type ListSnapshotsResponses = {
@ -1840,10 +1840,10 @@ export type ListSnapshotsResponse = ListSnapshotsResponses[keyof ListSnapshotsRe
export type RefreshSnapshotsData = {
body?: never;
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/repositories/{id}/snapshots/refresh";
url: "/api/v1/repositories/{shortId}/snapshots/refresh";
};
export type RefreshSnapshotsResponses = {
@ -1861,11 +1861,11 @@ export type RefreshSnapshotsResponse = RefreshSnapshotsResponses[keyof RefreshSn
export type DeleteSnapshotData = {
body?: never;
path: {
id: string;
shortId: string;
snapshotId: string;
};
query?: never;
url: "/api/v1/repositories/{id}/snapshots/{snapshotId}";
url: "/api/v1/repositories/{shortId}/snapshots/{snapshotId}";
};
export type DeleteSnapshotResponses = {
@ -1882,11 +1882,11 @@ export type DeleteSnapshotResponse = DeleteSnapshotResponses[keyof DeleteSnapsho
export type GetSnapshotDetailsData = {
body?: never;
path: {
id: string;
shortId: string;
snapshotId: string;
};
query?: never;
url: "/api/v1/repositories/{id}/snapshots/{snapshotId}";
url: "/api/v1/repositories/{shortId}/snapshots/{snapshotId}";
};
export type GetSnapshotDetailsResponses = {
@ -1926,7 +1926,7 @@ export type GetSnapshotDetailsResponse = GetSnapshotDetailsResponses[keyof GetSn
export type ListSnapshotFilesData = {
body?: never;
path: {
id: string;
shortId: string;
snapshotId: string;
};
query?: {
@ -1934,7 +1934,7 @@ export type ListSnapshotFilesData = {
offset?: string;
path?: string;
};
url: "/api/v1/repositories/{id}/snapshots/{snapshotId}/files";
url: "/api/v1/repositories/{shortId}/snapshots/{snapshotId}/files";
};
export type ListSnapshotFilesResponses = {
@ -1981,10 +1981,10 @@ export type RestoreSnapshotData = {
targetPath?: string;
};
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/repositories/{id}/restore";
url: "/api/v1/repositories/{shortId}/restore";
};
export type RestoreSnapshotResponses = {
@ -2004,10 +2004,10 @@ export type RestoreSnapshotResponse = RestoreSnapshotResponses[keyof RestoreSnap
export type CancelDoctorData = {
body?: never;
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/repositories/{id}/doctor";
url: "/api/v1/repositories/{shortId}/doctor";
};
export type CancelDoctorErrors = {
@ -2031,10 +2031,10 @@ export type CancelDoctorResponse = CancelDoctorResponses[keyof CancelDoctorRespo
export type StartDoctorData = {
body?: never;
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/repositories/{id}/doctor";
url: "/api/v1/repositories/{shortId}/doctor";
};
export type StartDoctorErrors = {
@ -2059,10 +2059,10 @@ export type StartDoctorResponse = StartDoctorResponses[keyof StartDoctorResponse
export type UnlockRepositoryData = {
body?: never;
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/repositories/{id}/unlock";
url: "/api/v1/repositories/{shortId}/unlock";
};
export type UnlockRepositoryResponses = {
@ -2085,10 +2085,10 @@ export type TagSnapshotsData = {
set?: Array<string>;
};
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/repositories/{id}/snapshots/tag";
url: "/api/v1/repositories/{shortId}/snapshots/tag";
};
export type TagSnapshotsResponses = {
@ -2108,10 +2108,10 @@ export type DevPanelExecData = {
args?: Array<string>;
};
path: {
id: string;
shortId: string;
};
query?: never;
url: "/api/v1/repositories/{id}/exec";
url: "/api/v1/repositories/{shortId}/exec";
};
export type DevPanelExecErrors = {
@ -2433,7 +2433,7 @@ export type CreateBackupScheduleData = {
enabled: boolean;
name: string;
repositoryId: string;
volumeId: number;
volumeId: number | string;
excludeIfPresent?: Array<string>;
excludePatterns?: Array<string>;
includePatterns?: Array<string>;
@ -2493,10 +2493,10 @@ export type CreateBackupScheduleResponse = CreateBackupScheduleResponses[keyof C
export type DeleteBackupScheduleData = {
body?: never;
path: {
scheduleId: string;
shortId: string;
};
query?: never;
url: "/api/v1/backups/{scheduleId}";
url: "/api/v1/backups/{shortId}";
};
export type DeleteBackupScheduleResponses = {
@ -2513,10 +2513,10 @@ export type DeleteBackupScheduleResponse = DeleteBackupScheduleResponses[keyof D
export type GetBackupScheduleData = {
body?: never;
path: {
scheduleId: string;
shortId: string;
};
query?: never;
url: "/api/v1/backups/{scheduleId}";
url: "/api/v1/backups/{shortId}";
};
export type GetBackupScheduleResponses = {
@ -2831,10 +2831,10 @@ export type UpdateBackupScheduleData = {
tags?: Array<string>;
};
path: {
scheduleId: string;
shortId: string;
};
query?: never;
url: "/api/v1/backups/{scheduleId}";
url: "/api/v1/backups/{shortId}";
};
export type UpdateBackupScheduleResponses = {
@ -2876,10 +2876,10 @@ export type UpdateBackupScheduleResponse = UpdateBackupScheduleResponses[keyof U
export type GetBackupScheduleForVolumeData = {
body?: never;
path: {
volumeId: string;
volumeShortId: string;
};
query?: never;
url: "/api/v1/backups/volume/{volumeId}";
url: "/api/v1/backups/volume/{volumeShortId}";
};
export type GetBackupScheduleForVolumeResponses = {
@ -3176,10 +3176,10 @@ export type GetBackupScheduleForVolumeResponse =
export type RunBackupNowData = {
body?: never;
path: {
scheduleId: string;
shortId: string;
};
query?: never;
url: "/api/v1/backups/{scheduleId}/run";
url: "/api/v1/backups/{shortId}/run";
};
export type RunBackupNowResponses = {
@ -3196,10 +3196,10 @@ export type RunBackupNowResponse = RunBackupNowResponses[keyof RunBackupNowRespo
export type StopBackupData = {
body?: never;
path: {
scheduleId: string;
shortId: string;
};
query?: never;
url: "/api/v1/backups/{scheduleId}/stop";
url: "/api/v1/backups/{shortId}/stop";
};
export type StopBackupErrors = {
@ -3223,10 +3223,10 @@ export type StopBackupResponse = StopBackupResponses[keyof StopBackupResponses];
export type RunForgetData = {
body?: never;
path: {
scheduleId: string;
shortId: string;
};
query?: never;
url: "/api/v1/backups/{scheduleId}/forget";
url: "/api/v1/backups/{shortId}/forget";
};
export type RunForgetResponses = {
@ -3243,10 +3243,10 @@ export type RunForgetResponse = RunForgetResponses[keyof RunForgetResponses];
export type GetScheduleNotificationsData = {
body?: never;
path: {
scheduleId: string;
shortId: string;
};
query?: never;
url: "/api/v1/backups/{scheduleId}/notifications";
url: "/api/v1/backups/{shortId}/notifications";
};
export type GetScheduleNotificationsResponses = {
@ -3355,10 +3355,10 @@ export type UpdateScheduleNotificationsData = {
}>;
};
path: {
scheduleId: string;
shortId: string;
};
query?: never;
url: "/api/v1/backups/{scheduleId}/notifications";
url: "/api/v1/backups/{shortId}/notifications";
};
export type UpdateScheduleNotificationsResponses = {
@ -3459,10 +3459,10 @@ export type UpdateScheduleNotificationsResponse =
export type GetScheduleMirrorsData = {
body?: never;
path: {
scheduleId: string;
shortId: string;
};
query?: never;
url: "/api/v1/backups/{scheduleId}/mirrors";
url: "/api/v1/backups/{shortId}/mirrors";
};
export type GetScheduleMirrorsResponses = {
@ -3664,7 +3664,7 @@ export type GetScheduleMirrorsResponses = {
updatedAt: number;
};
repositoryId: string;
scheduleId: number;
scheduleId: string;
}>;
};
@ -3678,10 +3678,10 @@ export type UpdateScheduleMirrorsData = {
}>;
};
path: {
scheduleId: string;
shortId: string;
};
query?: never;
url: "/api/v1/backups/{scheduleId}/mirrors";
url: "/api/v1/backups/{shortId}/mirrors";
};
export type UpdateScheduleMirrorsResponses = {
@ -3883,7 +3883,7 @@ export type UpdateScheduleMirrorsResponses = {
updatedAt: number;
};
repositoryId: string;
scheduleId: number;
scheduleId: string;
}>;
};
@ -3892,10 +3892,10 @@ export type UpdateScheduleMirrorsResponse = UpdateScheduleMirrorsResponses[keyof
export type GetMirrorCompatibilityData = {
body?: never;
path: {
scheduleId: string;
shortId: string;
};
query?: never;
url: "/api/v1/backups/{scheduleId}/mirrors/compatibility";
url: "/api/v1/backups/{shortId}/mirrors/compatibility";
};
export type GetMirrorCompatibilityResponses = {
@ -3913,7 +3913,7 @@ export type GetMirrorCompatibilityResponse = GetMirrorCompatibilityResponses[key
export type ReorderBackupSchedulesData = {
body?: {
scheduleIds: Array<number>;
scheduleShortIds: Array<string>;
};
path?: never;
query?: never;

View file

@ -82,7 +82,7 @@ export function DevPanel({ open, onOpenChange }: DevPanelProps) {
try {
const result = await devPanelExec({
path: { id: selectedRepoId },
path: { shortId: selectedRepoId },
body: { command, args: argsArray.length > 0 ? argsArray : undefined },
signal: abortControllerRef.current.signal,
});
@ -155,7 +155,7 @@ export function DevPanel({ open, onOpenChange }: DevPanelProps) {
</SelectTrigger>
<SelectContent>
{repositories.map((repo) => (
<SelectItem key={repo.id} value={repo.id}>
<SelectItem key={repo.id} value={repo.shortId}>
{repo.name} ({repo.type})
</SelectItem>
))}

View file

@ -34,7 +34,7 @@ export const SnapshotTreeBrowser = ({
const { data, isLoading, error } = useQuery({
...listSnapshotFilesOptions({
path: { id: repositoryId, snapshotId },
path: { shortId: repositoryId, snapshotId },
query: { path: normalizedBasePath },
}),
enabled,
@ -92,7 +92,7 @@ export const SnapshotTreeBrowser = ({
fetchFolder: async (path, offset = 0) => {
return await queryClient.ensureQueryData(
listSnapshotFilesOptions({
path: { id: repositoryId, snapshotId },
path: { shortId: repositoryId, snapshotId },
query: {
path,
offset: offset.toString(),
@ -104,7 +104,7 @@ export const SnapshotTreeBrowser = ({
prefetchFolder: (path) => {
void queryClient.prefetchQuery(
listSnapshotFilesOptions({
path: { id: repositoryId, snapshotId },
path: { shortId: repositoryId, snapshotId },
query: {
path,
offset: "0",

View file

@ -13,7 +13,7 @@ export const VolumeFileBrowser = ({ volumeId, enabled = true, ...uiProps }: Volu
const queryClient = useQueryClient();
const { data, isLoading, error } = useQuery({
...listFilesOptions({ path: { id: volumeId } }),
...listFilesOptions({ path: { shortId: volumeId } }),
enabled,
});
@ -23,7 +23,7 @@ export const VolumeFileBrowser = ({ volumeId, enabled = true, ...uiProps }: Volu
fetchFolder: async (path, offset): Promise<FetchFolderResult> => {
return await queryClient.ensureQueryData(
listFilesOptions({
path: { id: volumeId },
path: { shortId: volumeId },
query: { path, offset: offset?.toString() },
}),
);
@ -31,7 +31,7 @@ export const VolumeFileBrowser = ({ volumeId, enabled = true, ...uiProps }: Volu
prefetchFolder: (path) => {
void queryClient.prefetchQuery(
listFilesOptions({
path: { id: volumeId },
path: { shortId: volumeId },
query: { path },
}),
);

View file

@ -92,7 +92,7 @@ export function RestoreForm({ repository, snapshotId, returnPath, basePath }: Re
onError: (error) => {
restoreCompletedRef.current = true;
setIsRestoreActive(false);
handleRepositoryError("Restore failed", error, repository.id);
handleRepositoryError("Restore failed", error, repository.shortId);
},
});
@ -113,7 +113,7 @@ export function RestoreForm({ repository, snapshotId, returnPath, basePath }: Re
setShowRestoreResultAlert(false);
restoreSnapshot({
path: { id: repository.id },
path: { shortId: repository.shortId },
body: {
snapshotId,
include: includePaths.length > 0 ? includePaths : undefined,
@ -123,7 +123,7 @@ export function RestoreForm({ repository, snapshotId, returnPath, basePath }: Re
},
});
}, [
repository.id,
repository.shortId,
snapshotId,
excludeXattr,
restoreLocation,
@ -280,7 +280,7 @@ export function RestoreForm({ repository, snapshotId, returnPath, basePath }: Re
</CardHeader>
<CardContent className="flex-1 overflow-hidden flex flex-col p-0">
<SnapshotTreeBrowser
repositoryId={repository.id}
repositoryId={repository.shortId}
snapshotId={snapshotId}
basePath={volumeBasePath}
pageSize={500}

View file

@ -99,7 +99,7 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups, listSnapshots
const handleBulkDelete = () => {
toast.promise(
deleteSnapshots.mutateAsync({
path: { id: repositoryId },
path: { shortId: repositoryId },
body: { snapshotIds: Array.from(selectedIds) },
}),
{
@ -111,12 +111,12 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups, listSnapshots
};
const handleBulkReTag = () => {
const schedule = backups.find((b) => String(b.id) === targetScheduleId);
const schedule = backups.find((b) => b.shortId === targetScheduleId);
if (!schedule) return;
toast.promise(
tagSnapshots.mutateAsync({
path: { id: repositoryId },
path: { shortId: repositoryId },
body: {
snapshotIds: Array.from(selectedIds),
set: [schedule.shortId],
@ -187,7 +187,7 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups, listSnapshots
<Link
hidden={!backup}
to={backup ? `/backups/$backupId` : "."}
params={backup ? { backupId: String(backup.id) } : {}}
params={backup ? { backupId: backup.shortId } : {}}
onClick={(e) => e.stopPropagation()}
className="hover:underline"
>
@ -301,7 +301,7 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups, listSnapshots
</SelectTrigger>
<SelectContent>
{backups.map((backup) => (
<SelectItem key={backup.id} value={String(backup.id)}>
<SelectItem key={backup.shortId} value={backup.shortId}>
{backup.name}
</SelectItem>
))}

View file

@ -6,7 +6,7 @@ import type { PropsWithChildren } from "react";
interface SortableBackupCardProps {
isDragging?: boolean;
uniqueId: number;
uniqueId: string | number;
}
export function SortableCard({ isDragging, uniqueId, children }: PropsWithChildren<SortableBackupCardProps>) {

View file

@ -39,7 +39,7 @@ export const showLockErrorToast = (repositoryId: string, title: string) => {
toast.promise(
async () => {
const result = await unlockRepository({
path: { id: repositoryId },
path: { shortId: repositoryId },
throwOnError: true,
});
return result.data;

View file

@ -7,8 +7,8 @@ import { Link } from "@tanstack/react-router";
export const BackupCard = ({ schedule }: { schedule: BackupSchedule }) => {
return (
<Link key={schedule.id} to="/backups/$backupId" params={{ backupId: schedule.id.toString() }}>
<Card key={schedule.id} className="flex flex-col h-full">
<Link key={schedule.shortId} to="/backups/$backupId" params={{ backupId: schedule.shortId }}>
<Card key={schedule.shortId} className="flex flex-col h-full">
<CardHeader className="pb-3 overflow-hidden">
<div className="flex items-center justify-between gap-2 w-full">
<div className="flex items-center gap-2 flex-1 min-w-0 w-0">

View file

@ -7,22 +7,22 @@ import { formatDuration } from "~/utils/utils";
import { formatBytes } from "~/utils/format-bytes";
type Props = {
scheduleId: number;
scheduleShortId: string;
};
export const BackupProgressCard = ({ scheduleId }: Props) => {
export const BackupProgressCard = ({ scheduleShortId }: Props) => {
const { addEventListener } = useServerEvents();
const [progress, setProgress] = useState<BackupProgressEvent | null>(null);
useEffect(() => {
const unsubscribe = addEventListener("backup:progress", (progressData) => {
if (progressData.scheduleId === scheduleId) {
if (progressData.scheduleId === scheduleShortId) {
setProgress(progressData);
}
});
const unsubscribeComplete = addEventListener("backup:completed", (completedData) => {
if (completedData.scheduleId === scheduleId) {
if (completedData.scheduleId === scheduleShortId) {
setProgress(null);
}
});
@ -31,7 +31,7 @@ export const BackupProgressCard = ({ scheduleId }: Props) => {
unsubscribe();
unsubscribeComplete();
};
}, [addEventListener, scheduleId]);
}, [addEventListener, scheduleShortId]);
const percentDone = progress ? Math.round(progress.percent_done * 100) : 0;
const currentFile = progress?.current_files[0] || "";

View file

@ -48,7 +48,7 @@ export const BasicInfoSection = ({ form, volume }: BasicInfoSectionProps) => {
</SelectTrigger>
<SelectContent>
{repositoriesData?.map((repo) => (
<SelectItem key={repo.id} value={repo.id}>
<SelectItem key={repo.shortId} value={repo.shortId}>
<span className="flex items-center gap-2">
<RepositoryIcon backend={repo.type} />
{repo.name}

View file

@ -34,7 +34,9 @@ export const SummarySection = ({ volume, frequency, formValues }: SummarySection
</div>
<div>
<p className="text-xs uppercase text-muted-foreground">Repository</p>
<p className="font-medium">{repositoriesData?.find((r) => r.id === formValues.repositoryId)?.name || "—"}</p>
<p className="font-medium">
{repositoriesData?.find((r) => r.shortId === formValues.repositoryId)?.name || "—"}
</p>
</div>
{(formValues.includePatterns && formValues.includePatterns.length > 0) || formValues.includePatternsText ? (
<div>

View file

@ -2,9 +2,7 @@ import type { BackupSchedule } from "~/client/lib/types";
import { cronToFormValues } from "../../lib/cron-utils";
import type { InternalFormValues } from "./types";
export const backupScheduleToFormValues = (
schedule?: BackupSchedule,
): InternalFormValues | undefined => {
export const backupScheduleToFormValues = (schedule?: BackupSchedule): InternalFormValues | undefined => {
if (!schedule) {
return undefined;
}
@ -18,7 +16,7 @@ export const backupScheduleToFormValues = (
return {
name: schedule.name,
repositoryId: schedule.repositoryId,
repositoryId: schedule.repository.shortId,
includePatterns: fileBrowserPaths.length > 0 ? fileBrowserPaths : undefined,
includePatternsText: textPatterns.length > 0 ? textPatterns.join("\n") : undefined,
excludePatternsText: schedule.excludePatterns?.join("\n") || undefined,

View file

@ -25,7 +25,7 @@ import type { GetScheduleMirrorsResponse } from "~/client/api-client";
import { Link } from "@tanstack/react-router";
type Props = {
scheduleId: number;
scheduleShortId: string;
primaryRepositoryId: string;
repositories: Repository[];
initialData: GetScheduleMirrorsResponse;
@ -55,14 +55,14 @@ const buildAssignments = (mirrors: GetScheduleMirrorsResponse) =>
]),
);
export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, repositories, initialData }: Props) => {
export const ScheduleMirrorsConfig = ({ scheduleShortId, primaryRepositoryId, repositories, initialData }: Props) => {
const [assignments, setAssignments] = useState<Map<string, MirrorAssignment>>(() => buildAssignments(initialData));
const [hasChanges, setHasChanges] = useState(false);
const [isAddingNew, setIsAddingNew] = useState(false);
const { addEventListener } = useServerEvents();
const { data: currentMirrors } = useSuspenseQuery({
...getScheduleMirrorsOptions({ path: { scheduleId: scheduleId.toString() } }),
...getScheduleMirrorsOptions({ path: { shortId: scheduleShortId } }),
});
useEffect(() => {
@ -72,7 +72,7 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
}, [currentMirrors, hasChanges]);
const { data: compatibility } = useQuery({
...getMirrorCompatibilityOptions({ path: { scheduleId: scheduleId.toString() } }),
...getMirrorCompatibilityOptions({ path: { shortId: scheduleShortId } }),
});
const updateMirrors = useMutation({
@ -100,7 +100,7 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
useEffect(() => {
const unsubscribeStarted = addEventListener("mirror:started", (event) => {
if (event.scheduleId !== scheduleId) return;
if (event.scheduleId !== scheduleShortId) return;
setAssignments((prev) => {
const next = new Map(prev);
const existing = next.get(event.repositoryId);
@ -111,7 +111,7 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
});
const unsubscribeCompleted = addEventListener("mirror:completed", (event) => {
if (event.scheduleId !== scheduleId) return;
if (event.scheduleId !== scheduleShortId) return;
setAssignments((prev) => {
const next = new Map(prev);
const existing = next.get(event.repositoryId);
@ -130,7 +130,7 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
unsubscribeStarted();
unsubscribeCompleted();
};
}, [addEventListener, scheduleId]);
}, [addEventListener, scheduleShortId]);
const addRepository = (repositoryId: string) => {
const newAssignments = new Map(assignments);
@ -174,7 +174,7 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
enabled: a.enabled,
}));
updateMirrors.mutate({
path: { scheduleId: scheduleId.toString() },
path: { shortId: scheduleShortId },
body: {
mirrors: mirrorsList,
},
@ -188,18 +188,18 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
const selectableRepositories =
repositories?.filter((r) => {
if (r.id === primaryRepositoryId) return false;
if (assignments.has(r.id)) return false;
if (r.shortId === primaryRepositoryId) return false;
if (assignments.has(r.shortId)) return false;
return true;
}) || [];
const hasAvailableRepositories = selectableRepositories.some((r) => {
const compat = compatibilityMap.get(r.id);
const compat = compatibilityMap.get(r.shortId);
return compat?.compatible !== false;
});
const assignedRepositories = Array.from(assignments.keys())
.map((id) => repositories?.find((r) => r.id === id))
.map((id) => repositories?.find((r) => r.shortId === id))
.filter((r) => r !== undefined);
const getStatusVariant = (status: string | null) => {
@ -262,13 +262,13 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
</SelectTrigger>
<SelectContent>
{selectableRepositories.map((repository) => {
const compat = compatibilityMap.get(repository.id);
const compat = compatibilityMap.get(repository.shortId);
return (
<Tooltip key={repository.id}>
<Tooltip key={repository.shortId}>
<TooltipTrigger asChild>
<div>
<SelectItem value={repository.id} disabled={!compat?.compatible}>
<SelectItem value={repository.shortId} disabled={!compat?.compatible}>
<div className="flex items-center gap-2">
<RepositoryIcon backend={repository.type} className="h-4 w-4" />
<span>{repository.name}</span>
@ -322,16 +322,16 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
</TableHeader>
<TableBody>
{assignedRepositories.map((repository) => {
const assignment = assignments.get(repository.id);
const assignment = assignments.get(repository.shortId);
if (!assignment) return null;
return (
<TableRow key={repository.id}>
<TableRow key={repository.shortId}>
<TableCell>
<div className="flex items-center gap-2">
<Link
to="/repositories/$repositoryId"
params={{ repositoryId: repository.id }}
params={{ repositoryId: repository.shortId }}
className="hover:underline flex items-center gap-2"
>
<RepositoryIcon backend={repository.type} className="h-4 w-4" />
@ -346,7 +346,7 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
<Switch
className="align-middle"
checked={assignment.enabled}
onCheckedChange={() => toggleEnabled(repository.id)}
onCheckedChange={() => toggleEnabled(repository.shortId)}
/>
</TableCell>
<TableCell>
@ -363,7 +363,7 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
<Button
variant="ghost"
size="icon"
onClick={() => removeRepository(repository.id)}
onClick={() => removeRepository(repository.shortId)}
className="h-8 w-8 text-muted-foreground hover:text-destructive align-baseline"
>
<Trash2 className="h-4 w-4" />

View file

@ -17,7 +17,7 @@ import type { NotificationDestination } from "~/client/lib/types";
import type { GetScheduleNotificationsResponse } from "~/client/api-client";
type Props = {
scheduleId: number;
scheduleShortId: string;
destinations: NotificationDestination[];
initialData: GetScheduleNotificationsResponse;
};
@ -30,7 +30,7 @@ type NotificationAssignment = {
notifyOnFailure: boolean;
};
export const ScheduleNotificationsConfig = ({ scheduleId, destinations, initialData }: Props) => {
export const ScheduleNotificationsConfig = ({ scheduleShortId, destinations, initialData }: Props) => {
const map = new Map<number, NotificationAssignment>();
for (const assignment of initialData) {
map.set(assignment.destinationId, {
@ -47,7 +47,7 @@ export const ScheduleNotificationsConfig = ({ scheduleId, destinations, initialD
const [isAddingNew, setIsAddingNew] = useState(false);
const { data: currentAssignments } = useQuery({
...getScheduleNotificationsOptions({ path: { scheduleId: scheduleId.toString() } }),
...getScheduleNotificationsOptions({ path: { shortId: scheduleShortId } }),
initialData,
});
@ -107,7 +107,7 @@ export const ScheduleNotificationsConfig = ({ scheduleId, destinations, initialD
const handleSave = () => {
const assignmentsList = Array.from(assignments.values());
updateNotifications.mutate({
path: { scheduleId: scheduleId.toString() },
path: { shortId: scheduleShortId },
body: {
assignments: assignmentsList,
},

View file

@ -76,7 +76,7 @@ export const ScheduleSummary = (props: Props) => {
const handleConfirmForget = () => {
setShowForgetConfirm(false);
runForget.mutate({ path: { scheduleId: schedule.id.toString() } });
runForget.mutate({ path: { shortId: schedule.shortId } });
};
const handleConfirmStop = () => {
@ -215,7 +215,7 @@ export const ScheduleSummary = (props: Props) => {
</CardContent>
</Card>
{schedule.lastBackupStatus === "in_progress" && <BackupProgressCard scheduleId={schedule.id} />}
{schedule.lastBackupStatus === "in_progress" && <BackupProgressCard scheduleShortId={schedule.shortId} />}
<AlertDialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
<AlertDialogContent>

View file

@ -36,7 +36,7 @@ interface ScheduleAwareTreeBrowserProps {
const ScheduleAwareTreeBrowser = ({ scheduleShortId, repositoryId, snapshotId }: ScheduleAwareTreeBrowserProps) => {
const { data: schedule, isPending } = useQuery({
...getBackupScheduleOptions({ path: { scheduleId: scheduleShortId } }),
...getBackupScheduleOptions({ path: { shortId: scheduleShortId } }),
retry: false,
});

View file

@ -69,7 +69,7 @@ export function ScheduleDetailsPage(props: Props) {
const [snapshotToDelete, setSnapshotToDelete] = useState<string | null>(null);
const { data: schedule } = useSuspenseQuery({
...getBackupScheduleOptions({ path: { scheduleId } }),
...getBackupScheduleOptions({ path: { shortId: scheduleId } }),
});
const {
@ -77,7 +77,7 @@ export function ScheduleDetailsPage(props: Props) {
isLoading,
failureReason,
} = useQuery({
...listSnapshotsOptions({ path: { id: schedule.repository.id }, query: { backupId: schedule.shortId } }),
...listSnapshotsOptions({ path: { shortId: schedule.repository.shortId }, query: { backupId: schedule.shortId } }),
});
const updateSchedule = useMutation({
@ -124,7 +124,10 @@ export function ScheduleDetailsPage(props: Props) {
},
});
const listSnapshotsQueryOptions = { path: { id: schedule.repository.id }, query: { backupId: schedule.shortId } };
const listSnapshotsQueryOptions = {
path: { shortId: schedule.repository.shortId },
query: { backupId: schedule.shortId },
};
const deleteSnapshot = useMutation({
...deleteSnapshotMutation(),
@ -167,7 +170,7 @@ export function ScheduleDetailsPage(props: Props) {
if (formValues.keepYearly) retentionPolicy.keepYearly = formValues.keepYearly;
updateSchedule.mutate({
path: { scheduleId: schedule.id.toString() },
path: { shortId: schedule.shortId },
body: {
name: formValues.name,
repositoryId: formValues.repositoryId,
@ -184,7 +187,7 @@ export function ScheduleDetailsPage(props: Props) {
const handleToggleEnabled = (enabled: boolean) => {
updateSchedule.mutate({
path: { scheduleId: schedule.id.toString() },
path: { shortId: schedule.shortId },
body: {
repositoryId: schedule.repositoryId,
enabled,
@ -207,7 +210,7 @@ export function ScheduleDetailsPage(props: Props) {
if (snapshotToDelete) {
toast.promise(
deleteSnapshot.mutateAsync({
path: { id: schedule.repository.shortId, snapshotId: snapshotToDelete },
path: { shortId: schedule.repository.shortId, snapshotId: snapshotToDelete },
}),
{
loading: "Deleting snapshot...",
@ -251,23 +254,23 @@ export function ScheduleDetailsPage(props: Props) {
<div className="flex flex-col gap-6">
<ScheduleSummary
handleToggleEnabled={handleToggleEnabled}
handleRunBackupNow={() => runBackupNow.mutate({ path: { scheduleId: schedule.id.toString() } })}
handleStopBackup={() => stopBackup.mutate({ path: { scheduleId: schedule.id.toString() } })}
handleDeleteSchedule={() => deleteSchedule.mutate({ path: { scheduleId: schedule.id.toString() } })}
handleRunBackupNow={() => runBackupNow.mutate({ path: { shortId: schedule.shortId } })}
handleStopBackup={() => stopBackup.mutate({ path: { shortId: schedule.shortId } })}
handleDeleteSchedule={() => deleteSchedule.mutate({ path: { shortId: schedule.shortId } })}
setIsEditMode={setIsEditMode}
schedule={schedule}
/>
<div className={cn({ hidden: !loaderData.notifs?.length })}>
<ScheduleNotificationsConfig
scheduleId={schedule.id}
scheduleShortId={schedule.shortId}
destinations={loaderData.notifs ?? []}
initialData={loaderData.scheduleNotifs ?? []}
/>
</div>
<div className={cn({ hidden: !loaderData.repos?.length || loaderData.repos.length < 2 })}>
<ScheduleMirrorsConfig
scheduleId={schedule.id}
primaryRepositoryId={schedule.repositoryId}
scheduleShortId={schedule.shortId}
primaryRepositoryId={schedule.repository.shortId}
repositories={loaderData.repos ?? []}
initialData={loaderData.mirrors ?? []}
/>
@ -285,7 +288,7 @@ export function ScheduleDetailsPage(props: Props) {
key={selectedSnapshot?.short_id}
snapshot={selectedSnapshot}
repositoryId={schedule.repository.shortId}
backupId={schedule.id.toString()}
backupId={schedule.shortId}
onDeleteSnapshot={handleDeleteSnapshot}
isDeletingSnapshot={deleteSnapshot.isPending}
/>

View file

@ -27,8 +27,8 @@ export function BackupsPage() {
...listBackupSchedulesOptions(),
});
const [localItems, setLocalItems] = useState<number[] | null>(null);
const items = localItems ?? schedules?.map((s) => s.id) ?? [];
const [localItems, setLocalItems] = useState<string[] | null>(null);
const items = localItems ?? schedules?.map((s) => s.shortId) ?? [];
const sensors = useSensors(
useSensor(PointerSensor, {
@ -48,14 +48,14 @@ export function BackupsPage() {
if (over && active.id !== over.id) {
setLocalItems((currentItems) => {
const baseItems = currentItems ?? schedules?.map((s) => s.id) ?? [];
const activeId = active.id as number;
const overId = over.id as number;
const baseItems = currentItems ?? schedules?.map((s) => s.shortId) ?? [];
const activeId = String(active.id);
const overId = String(over.id);
let oldIndex = baseItems.indexOf(activeId);
let newIndex = baseItems.indexOf(overId);
if (oldIndex === -1 || newIndex === -1) {
const freshItems = schedules?.map((s) => s.id) ?? [];
const freshItems = schedules?.map((s) => s.shortId) ?? [];
oldIndex = freshItems.indexOf(activeId);
newIndex = freshItems.indexOf(overId);
@ -64,12 +64,12 @@ export function BackupsPage() {
}
const newItems = arrayMove(freshItems, oldIndex, newIndex);
reorderMutation.mutate({ body: { scheduleIds: newItems } });
reorderMutation.mutate({ body: { scheduleShortIds: newItems } });
return newItems;
}
const newItems = arrayMove(baseItems, oldIndex, newIndex);
reorderMutation.mutate({ body: { scheduleIds: newItems } });
reorderMutation.mutate({ body: { scheduleShortIds: newItems } });
return newItems;
});
@ -94,7 +94,7 @@ export function BackupsPage() {
);
}
const scheduleMap = new Map(schedules.map((s) => [s.id, s]));
const scheduleMap = new Map(schedules.map((s) => [s.shortId, s]));
return (
<div className="container @container mx-auto space-y-6">

View file

@ -19,7 +19,7 @@ import { Link, useNavigate } from "@tanstack/react-router";
export function CreateBackupPage() {
const navigate = useNavigate();
const formId = useId();
const [selectedVolumeId, setSelectedVolumeId] = useState<number | undefined>();
const [selectedVolumeShortId, setSelectedVolumeShortId] = useState<string | undefined>();
const { data: volumesData } = useSuspenseQuery({
...listVolumesOptions(),
@ -33,7 +33,7 @@ export function CreateBackupPage() {
...createBackupScheduleMutation(),
onSuccess: (data) => {
toast.success("Backup job created successfully");
void navigate({ to: `/backups/${data.id}` });
void navigate({ to: `/backups/${data.shortId}` });
},
onError: (error) => {
toast.error("Failed to create backup job", {
@ -43,7 +43,7 @@ export function CreateBackupPage() {
});
const handleSubmit = (formValues: BackupScheduleFormValues) => {
if (!selectedVolumeId) return;
if (!selectedVolumeShortId) return;
const cronExpression = getCronExpression(
formValues.frequency,
@ -64,7 +64,7 @@ export function CreateBackupPage() {
createSchedule.mutate({
body: {
name: formValues.name,
volumeId: selectedVolumeId,
volumeId: selectedVolumeShortId,
repositoryId: formValues.repositoryId,
enabled: true,
cronExpression,
@ -77,7 +77,7 @@ export function CreateBackupPage() {
});
};
const selectedVolume = volumesData.find((v) => v.id === selectedVolumeId);
const selectedVolume = volumesData.find((v) => v.shortId === selectedVolumeShortId);
if (!volumesData.length) {
return (
@ -113,13 +113,13 @@ export function CreateBackupPage() {
<div className="container mx-auto space-y-4">
<Card>
<CardContent>
<Select value={selectedVolumeId?.toString()} onValueChange={(v) => setSelectedVolumeId(Number(v))}>
<Select value={selectedVolumeShortId} onValueChange={setSelectedVolumeShortId}>
<SelectTrigger id="volume-select">
<SelectValue placeholder="Choose a volume to backup" />
</SelectTrigger>
<SelectContent>
{volumesData.map((volume) => (
<SelectItem key={volume.id} value={volume.id.toString()}>
<SelectItem key={volume.shortId} value={volume.shortId}>
<span className="flex items-center gap-2">
<HardDrive className="h-4 w-4" />
{volume.name}

View file

@ -49,7 +49,7 @@ export function EditRepositoryPage({ repositoryId }: { repositoryId: string }) {
const [pendingValues, setPendingValues] = useState<RepositoryFormValues | null>(null);
const { data: repository } = useSuspenseQuery({
...getRepositoryOptions({ path: { id: repositoryId } }),
...getRepositoryOptions({ path: { shortId: repositoryId } }),
});
const updateRepository = useMutation({
@ -76,7 +76,7 @@ export function EditRepositoryPage({ repositoryId }: { repositoryId: string }) {
const submitUpdate = (values: RepositoryFormValues) => {
updateRepository.mutate({
path: { id: repositoryId },
path: { shortId: repositoryId },
body: {
name: values.name,
compressionMode: values.compressionMode,

View file

@ -12,7 +12,7 @@ export default function RepositoryDetailsPage({ repositoryId }: { repositoryId:
const activeTab = tab || "info";
const { data } = useSuspenseQuery({
...getRepositoryOptions({ path: { id: repositoryId } }),
...getRepositoryOptions({ path: { shortId: repositoryId } }),
});
return (

View file

@ -53,7 +53,7 @@ export function SnapshotDetailsPage({ repositoryId, snapshotId }: { repositoryId
const [showAllPaths, setShowAllPaths] = useState(false);
const { data: repository } = useSuspenseQuery({
...getRepositoryOptions({ path: { id: repositoryId } }),
...getRepositoryOptions({ path: { shortId: repositoryId } }),
});
const { data: schedules } = useSuspenseQuery({
@ -61,7 +61,7 @@ export function SnapshotDetailsPage({ repositoryId, snapshotId }: { repositoryId
});
const { data, error } = useQuery({
...getSnapshotDetailsOptions({ path: { id: repositoryId, snapshotId: snapshotId } }),
...getSnapshotDetailsOptions({ path: { shortId: repositoryId, snapshotId: snapshotId } }),
});
const backupSchedule = schedules?.find((s) => data?.tags?.includes(s.shortId));

View file

@ -89,7 +89,7 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => {
const handleConfirmDelete = () => {
setShowDeleteConfirm(false);
deleteRepo.mutate({ path: { id: repository.id } });
deleteRepo.mutate({ path: { shortId: repository.shortId } });
};
const config = repository.config as RepositoryConfig;
@ -115,7 +115,7 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => {
type="button"
variant="destructive"
loading={cancelDoctor.isPending}
onClick={() => cancelDoctor.mutate({ path: { id: repository.id } })}
onClick={() => cancelDoctor.mutate({ path: { shortId: repository.shortId } })}
>
<Square className="h-4 w-4 mr-2" />
<span>Cancel doctor</span>
@ -123,7 +123,7 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => {
) : (
<Button
type="button"
onClick={() => startDoctor.mutate({ path: { id: repository.id } })}
onClick={() => startDoctor.mutate({ path: { shortId: repository.shortId } })}
disabled={startDoctor.isPending}
>
<Stethoscope className="h-4 w-4 mr-2" />
@ -133,7 +133,7 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => {
<Button
type="button"
variant="outline"
onClick={() => unlockRepo.mutate({ path: { id: repository.id } })}
onClick={() => unlockRepo.mutate({ path: { shortId: repository.shortId } })}
loading={unlockRepo.isPending}
>
<Unlock className="h-4 w-4 mr-2" />

View file

@ -22,7 +22,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => {
const [searchQuery, setSearchQuery] = useState("");
const { data, isFetching, failureReason } = useQuery({
...listSnapshotsOptions({ path: { id: repository.id } }),
...listSnapshotsOptions({ path: { shortId: repository.shortId } }),
initialData: [],
});
@ -41,7 +41,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => {
});
const handleRefresh = () => {
refreshMutation.mutate({ path: { id: repository.id } });
refreshMutation.mutate({ path: { shortId: repository.shortId } });
};
const filteredSnapshots = data.filter((snapshot: Snapshot) => {
@ -173,7 +173,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => {
snapshots={filteredSnapshots}
repositoryId={repository.shortId}
backups={schedules.data ?? []}
listSnapshotsQueryOptions={{ path: { id: repository.id } }}
listSnapshotsQueryOptions={{ path: { shortId: repository.shortId } }}
/>
)}
<div className="px-4 py-2 text-sm text-muted-foreground bg-card-header flex justify-between border-t">

View file

@ -60,7 +60,10 @@ export const HealthchecksCard = ({ volume }: Props) => {
<OnOff
isOn={volume.autoRemount}
toggle={() =>
toggleAutoRemount.mutate({ path: { id: volume.shortId }, body: { autoRemount: !volume.autoRemount } })
toggleAutoRemount.mutate({
path: { shortId: volume.shortId },
body: { autoRemount: !volume.autoRemount },
})
}
disabled={toggleAutoRemount.isPending}
enabledLabel="Enabled"
@ -74,7 +77,7 @@ export const HealthchecksCard = ({ volume }: Props) => {
variant="outline"
className="mt-4"
loading={healthcheck.isPending}
onClick={() => healthcheck.mutate({ path: { id: volume.shortId } })}
onClick={() => healthcheck.mutate({ path: { shortId: volume.shortId } })}
>
<Activity className="h-4 w-4 mr-2" />
Run Health Check

View file

@ -14,7 +14,7 @@ export function VolumeDetails({ volumeId }: { volumeId: string }) {
const activeTab = searchParams.tab || "info";
const { data } = useSuspenseQuery({
...getVolumeOptions({ path: { id: volumeId } }),
...getVolumeOptions({ path: { shortId: volumeId } }),
});
const { volume, statfs } = data;

View file

@ -103,7 +103,7 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
const confirmUpdate = () => {
if (pendingValues) {
updateMutation.mutate({
path: { id: volume.shortId },
path: { shortId: volume.shortId },
body: { name: pendingValues.name, config: pendingValues },
});
}
@ -111,7 +111,7 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
const handleConfirmDelete = () => {
setShowDeleteConfirm(false);
deleteVol.mutate({ path: { id: volume.shortId } });
deleteVol.mutate({ path: { shortId: volume.shortId } });
};
return (
@ -126,7 +126,7 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
{volume.status !== "mounted" ? (
<Button
type="button"
onClick={() => mountVol.mutate({ path: { id: volume.shortId } })}
onClick={() => mountVol.mutate({ path: { shortId: volume.shortId } })}
loading={mountVol.isPending}
>
<Plug className="h-4 w-4 mr-2" />
@ -136,7 +136,7 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
<Button
type="button"
variant="secondary"
onClick={() => unmountVol.mutate({ path: { id: volume.shortId } })}
onClick={() => unmountVol.mutate({ path: { shortId: volume.shortId } })}
loading={unmountVol.isPending}
>
<Unplug className="h-4 w-4 mr-2" />

View file

@ -7,7 +7,7 @@ import { getVolumeMountPath } from "~/client/lib/volume-path";
export const Route = createFileRoute("/(dashboard)/backups/$backupId/$snapshotId/restore")({
component: RouteComponent,
loader: async ({ params, context }) => {
const schedule = await getBackupSchedule({ path: { scheduleId: params.backupId } });
const schedule = await getBackupSchedule({ path: { shortId: params.backupId } });
if (!schedule.data) {
throw new Response("Not Found", { status: 404 });
@ -15,9 +15,13 @@ export const Route = createFileRoute("/(dashboard)/backups/$backupId/$snapshotId
const [snapshot, repository] = await Promise.all([
context.queryClient.ensureQueryData({
...getSnapshotDetailsOptions({ path: { id: schedule.data?.repositoryId, snapshotId: params.snapshotId } }),
...getSnapshotDetailsOptions({
path: { shortId: schedule.data.repository.shortId, snapshotId: params.snapshotId },
}),
}),
context.queryClient.ensureQueryData({
...getRepositoryOptions({ path: { shortId: schedule.data.repository.shortId } }),
}),
context.queryClient.ensureQueryData({ ...getRepositoryOptions({ path: { id: schedule.data?.repositoryId } }) }),
]);
return {

View file

@ -17,16 +17,19 @@ export const Route = createFileRoute("/(dashboard)/backups/$backupId/")({
const { backupId } = params;
const [schedule, notifs, repos, scheduleNotifs, mirrors] = await Promise.all([
context.queryClient.ensureQueryData({ ...getBackupScheduleOptions({ path: { scheduleId: backupId } }) }),
context.queryClient.ensureQueryData({ ...getBackupScheduleOptions({ path: { shortId: backupId } }) }),
context.queryClient.ensureQueryData({ ...listNotificationDestinationsOptions() }),
context.queryClient.ensureQueryData({ ...listRepositoriesOptions() }),
context.queryClient.ensureQueryData({ ...getScheduleNotificationsOptions({ path: { scheduleId: backupId } }) }),
context.queryClient.ensureQueryData({ ...getScheduleMirrorsOptions({ path: { scheduleId: backupId } }) }),
])
context.queryClient.ensureQueryData({ ...getScheduleNotificationsOptions({ path: { shortId: backupId } }) }),
context.queryClient.ensureQueryData({ ...getScheduleMirrorsOptions({ path: { shortId: backupId } }) }),
]);
void context.queryClient.prefetchQuery({
...listSnapshotsOptions({ path: { id: schedule.repository.id }, query: { backupId: schedule.shortId } }),
})
...listSnapshotsOptions({
path: { shortId: schedule.repository.shortId },
query: { backupId: schedule.shortId },
}),
});
return { schedule, notifs, repos, scheduleNotifs, mirrors };
},

View file

@ -7,7 +7,7 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repositoryId/$s
errorComponent: (e) => <div>{e.error.message}</div>,
loader: async ({ params, context }) => {
const res = await context.queryClient.ensureQueryData({
...getRepositoryOptions({ path: { id: params.repositoryId } }),
...getRepositoryOptions({ path: { shortId: params.repositoryId } }),
});
return res;

View file

@ -10,15 +10,15 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repositoryId/$s
loader: async ({ params, context }) => {
const [snapshot, repository] = await Promise.all([
context.queryClient.ensureQueryData({
...getSnapshotDetailsOptions({ path: { id: params.repositoryId, snapshotId: params.snapshotId } }),
...getSnapshotDetailsOptions({ path: { shortId: params.repositoryId, snapshotId: params.snapshotId } }),
}),
context.queryClient.ensureQueryData({ ...getRepositoryOptions({ path: { id: params.repositoryId } }) }),
context.queryClient.ensureQueryData({ ...getRepositoryOptions({ path: { shortId: params.repositoryId } }) }),
]);
let basePath: string | undefined;
const scheduleShortId = snapshot.tags?.[0];
if (scheduleShortId) {
const scheduleRes = await getBackupSchedule({ path: { scheduleId: scheduleShortId } });
const scheduleRes = await getBackupSchedule({ path: { shortId: scheduleShortId } });
if (scheduleRes.data) {
basePath = getVolumeMountPath(scheduleRes.data.volume);
}

View file

@ -7,8 +7,8 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repositoryId/ed
errorComponent: (e) => <div>{e.error.message}</div>,
loader: async ({ params, context }) => {
const repository = await context.queryClient.ensureQueryData({
...getRepositoryOptions({ path: { id: params.repositoryId } }),
})
...getRepositoryOptions({ path: { shortId: params.repositoryId } }),
});
return repository;
},

View file

@ -12,15 +12,15 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repositoryId/")
errorComponent: (e) => <div>{e.error.message}</div>,
loader: async ({ params, context }) => {
void context.queryClient.prefetchQuery({
...listSnapshotsOptions({ path: { id: params.repositoryId } }),
})
...listSnapshotsOptions({ path: { shortId: params.repositoryId } }),
});
void context.queryClient.prefetchQuery({
...listBackupSchedulesOptions(),
})
});
const res = await context.queryClient.ensureQueryData({
...getRepositoryOptions({ path: { id: params.repositoryId } }),
})
...getRepositoryOptions({ path: { shortId: params.repositoryId } }),
});
return res;
},

View file

@ -8,7 +8,7 @@ export const Route = createFileRoute("/(dashboard)/volumes/$volumeId")({
errorComponent: (e) => <div>{e.error.message}</div>,
loader: async ({ params, context }) => {
const res = await context.queryClient.ensureQueryData({
...getVolumeOptions({ path: { id: params.volumeId } }),
...getVolumeOptions({ path: { shortId: params.volumeId } }),
});
return res;

View file

@ -5,7 +5,7 @@ export const backupEventStatusSchema = type("'success' | 'error' | 'stopped' | '
export const restoreEventStatusSchema = type("'success' | 'error'");
const backupEventBaseSchema = type({
scheduleId: "number",
scheduleId: "string",
volumeName: "string",
repositoryName: "string",
});

View file

@ -23,13 +23,13 @@ export const serverEventPayloads = {
"restore:completed": payload<ServerRestoreCompletedEventDto>(),
"mirror:started": payload<{
organizationId: string;
scheduleId: number;
scheduleId: string;
repositoryId: string;
repositoryName: string;
}>(),
"mirror:completed": payload<{
organizationId: string;
scheduleId: number;
scheduleId: string;
repositoryId: string;
repositoryName: string;
status: "success" | "error";

View file

@ -61,17 +61,17 @@ describe("multi-organization isolation", () => {
expect(session1.organizationId).not.toBe(session2.organizationId);
const repoId = crypto.randomUUID();
const shortId = generateShortId();
const repoShortId = generateShortId();
await db.insert(repositoriesTable).values({
id: repoId,
shortId,
shortId: repoShortId,
name: "Org 1 Repo",
type: "local",
config: { backend: "local", name: "org1repo", path: "/tmp/repo1" },
organizationId: session1.organizationId,
});
const res = await app.request(`/api/v1/repositories/${repoId}`, {
const res = await app.request(`/api/v1/repositories/${repoShortId}`, {
headers: getAuthHeaders(session2.token),
});
@ -79,7 +79,7 @@ describe("multi-organization isolation", () => {
const body = await res.json();
expect(body.message).toBe("Repository not found");
const resOk = await app.request(`/api/v1/repositories/${repoId}`, {
const resOk = await app.request(`/api/v1/repositories/${repoShortId}`, {
headers: getAuthHeaders(session1.token),
});
expect(resOk.status).toBe(200);
@ -128,9 +128,10 @@ describe("multi-organization isolation", () => {
const session2 = await createTestSession();
const volumeId = Math.floor(Math.random() * 1000000);
const volumeShortId = generateShortId();
await db.insert(volumesTable).values({
id: volumeId,
shortId: generateShortId(),
shortId: volumeShortId,
name: "Org 1 Volume",
type: "directory",
config: { backend: "directory", path: "/tmp/vol1" },
@ -138,7 +139,7 @@ describe("multi-organization isolation", () => {
status: "unmounted",
});
const res = await app.request(`/api/v1/volumes/${volumeId}`, {
const res = await app.request(`/api/v1/volumes/${volumeShortId}`, {
headers: getAuthHeaders(session2.token),
});
@ -224,13 +225,13 @@ describe("multi-organization isolation", () => {
})
.returning();
const res = await app.request(`/api/v1/backups/${schedule.id}`, {
const res = await app.request(`/api/v1/backups/${schedule.shortId}`, {
headers: getAuthHeaders(session2.token),
});
expect(res.status).toBe(404);
const resOk = await app.request(`/api/v1/backups/${schedule.id}`, {
const resOk = await app.request(`/api/v1/backups/${schedule.shortId}`, {
headers: getAuthHeaders(session1.token),
});
expect(resOk.status).toBe(200);
@ -293,12 +294,12 @@ describe("multi-organization isolation", () => {
notifyOnFailure: true,
});
const resGet = await app.request(`/api/v1/backups/${schedule.id}/notifications`, {
const resGet = await app.request(`/api/v1/backups/${schedule.shortId}/notifications`, {
headers: getAuthHeaders(session2.token),
});
expect(resGet.status).toBe(404);
const resPut = await app.request(`/api/v1/backups/${schedule.id}/notifications`, {
const resPut = await app.request(`/api/v1/backups/${schedule.shortId}/notifications`, {
method: "PUT",
headers: {
...getAuthHeaders(session2.token),

View file

@ -222,10 +222,8 @@ describe("getScheduleByIdOrShortId", () => {
organizationId: otherOrgId,
});
await expect(backupsService.getScheduleByIdOrShortId(schedule.shortId)).rejects.toThrow(
"Backup schedule not found",
);
await expect(backupsService.getScheduleByIdOrShortId(schedule.id)).rejects.toThrow("Backup schedule not found");
expect(backupsService.getScheduleByIdOrShortId(schedule.shortId)).rejects.toThrow("Backup schedule not found");
expect(backupsService.getScheduleByIdOrShortId(schedule.id)).rejects.toThrow("Backup schedule not found");
});
});

View file

@ -52,15 +52,15 @@ export const backupScheduleController = new Hono()
return c.json<ListBackupSchedulesResponseDto>(schedules, 200);
})
.get("/:scheduleId", getBackupScheduleDto, async (c) => {
const scheduleId = c.req.param("scheduleId");
const schedule = await backupsService.getScheduleByIdOrShortId(scheduleId);
.get("/:shortId", getBackupScheduleDto, async (c) => {
const shortId = c.req.param("shortId");
const schedule = await backupsService.getScheduleByShortId(shortId);
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));
.get("/volume/:volumeShortId", getBackupScheduleForVolumeDto, async (c) => {
const volumeShortId = c.req.param("volumeShortId");
const schedule = await backupsService.getScheduleForVolume(volumeShortId);
return c.json<GetBackupScheduleForVolumeResponseDto>(schedule, 200);
})
@ -70,22 +70,23 @@ export const backupScheduleController = new Hono()
return c.json<CreateBackupScheduleDto>(schedule, 201);
})
.patch("/:scheduleId", updateBackupScheduleDto, validator("json", updateBackupScheduleBody), async (c) => {
const scheduleId = c.req.param("scheduleId");
.patch("/:shortId", updateBackupScheduleDto, validator("json", updateBackupScheduleBody), async (c) => {
const shortId = c.req.param("shortId");
const body = c.req.valid("json");
const schedule = await backupsService.updateSchedule(Number(scheduleId), body);
const schedule = await backupsService.updateSchedule(shortId, body);
return c.json<UpdateBackupScheduleDto>(schedule, 200);
})
.delete("/:scheduleId", deleteBackupScheduleDto, async (c) => {
const scheduleId = c.req.param("scheduleId");
await backupsService.deleteSchedule(Number(scheduleId));
.delete("/:shortId", deleteBackupScheduleDto, async (c) => {
const shortId = c.req.param("shortId");
await backupsService.deleteSchedule(shortId);
return c.json<DeleteBackupScheduleDto>({ success: true }, 200);
})
.post("/:scheduleId/run", runBackupNowDto, async (c) => {
const scheduleId = c.req.param("scheduleId");
const result = await backupsExecutionService.validateBackupExecution(Number(scheduleId), true);
.post("/:shortId/run", runBackupNowDto, async (c) => {
const shortId = c.req.param("shortId");
const schedule = await backupsService.getScheduleByShortId(shortId);
const result = await backupsExecutionService.validateBackupExecution(schedule.id, true);
if (result.type === "failure") {
throw result.error;
@ -95,64 +96,68 @@ export const backupScheduleController = new Hono()
return c.json<RunBackupNowDto>({ success: true }, 200);
}
backupsExecutionService.executeBackup(Number(scheduleId), true).catch((err) => {
logger.error(`Error executing manual backup for schedule ${scheduleId}:`, err);
backupsExecutionService.executeBackup(schedule.id, true).catch((err) => {
logger.error(`Error executing manual backup for schedule ${shortId}:`, err);
});
return c.json<RunBackupNowDto>({ success: true }, 200);
})
.post("/:scheduleId/stop", stopBackupDto, async (c) => {
const scheduleId = c.req.param("scheduleId");
await backupsExecutionService.stopBackup(Number(scheduleId));
.post("/:shortId/stop", stopBackupDto, async (c) => {
const shortId = c.req.param("shortId");
const schedule = await backupsService.getScheduleByShortId(shortId);
await backupsExecutionService.stopBackup(schedule.id);
return c.json<StopBackupDto>({ success: true }, 200);
})
.post("/:scheduleId/forget", runForgetDto, async (c) => {
const scheduleId = c.req.param("scheduleId");
await backupsExecutionService.runForget(Number(scheduleId));
.post("/:shortId/forget", runForgetDto, async (c) => {
const shortId = c.req.param("shortId");
const schedule = await backupsService.getScheduleByShortId(shortId);
await backupsExecutionService.runForget(schedule.id);
return c.json<RunForgetDto>({ success: true }, 200);
})
.get("/:scheduleId/notifications", getScheduleNotificationsDto, async (c) => {
const scheduleId = Number.parseInt(c.req.param("scheduleId"), 10);
const assignments = await notificationsService.getScheduleNotifications(scheduleId);
.get("/:shortId/notifications", getScheduleNotificationsDto, async (c) => {
const shortId = c.req.param("shortId");
const schedule = await backupsService.getScheduleByShortId(shortId);
const assignments = await notificationsService.getScheduleNotifications(schedule.id);
return c.json<GetScheduleNotificationsDto>(assignments, 200);
})
.put(
"/:scheduleId/notifications",
"/:shortId/notifications",
updateScheduleNotificationsDto,
validator("json", updateScheduleNotificationsBody),
async (c) => {
const scheduleId = Number.parseInt(c.req.param("scheduleId"), 10);
const shortId = c.req.param("shortId");
const schedule = await backupsService.getScheduleByShortId(shortId);
const body = c.req.valid("json");
const assignments = await notificationsService.updateScheduleNotifications(scheduleId, body.assignments);
const assignments = await notificationsService.updateScheduleNotifications(schedule.id, body.assignments);
return c.json<UpdateScheduleNotificationsDto>(assignments, 200);
},
)
.get("/:scheduleId/mirrors", getScheduleMirrorsDto, async (c) => {
const scheduleId = Number.parseInt(c.req.param("scheduleId"), 10);
const mirrors = await backupsService.getMirrors(scheduleId);
.get("/:shortId/mirrors", getScheduleMirrorsDto, async (c) => {
const shortId = c.req.param("shortId");
const mirrors = await backupsService.getMirrors(shortId);
return c.json<GetScheduleMirrorsDto>(mirrors, 200);
})
.put("/:scheduleId/mirrors", updateScheduleMirrorsDto, validator("json", updateScheduleMirrorsBody), async (c) => {
const scheduleId = Number.parseInt(c.req.param("scheduleId"), 10);
.put("/:shortId/mirrors", updateScheduleMirrorsDto, validator("json", updateScheduleMirrorsBody), async (c) => {
const shortId = c.req.param("shortId");
const body = c.req.valid("json");
const mirrors = await backupsService.updateMirrors(scheduleId, body);
const mirrors = await backupsService.updateMirrors(shortId, body);
return c.json<UpdateScheduleMirrorsDto>(mirrors, 200);
})
.get("/:scheduleId/mirrors/compatibility", getMirrorCompatibilityDto, async (c) => {
const scheduleId = Number.parseInt(c.req.param("scheduleId"), 10);
const compatibility = await backupsService.getMirrorCompatibility(scheduleId);
.get("/:shortId/mirrors/compatibility", getMirrorCompatibilityDto, async (c) => {
const shortId = c.req.param("shortId");
const compatibility = await backupsService.getMirrorCompatibility(shortId);
return c.json<GetMirrorCompatibilityDto>(compatibility, 200);
})
.post("/reorder", reorderBackupSchedulesDto, validator("json", reorderBackupSchedulesBody), async (c) => {
const body = c.req.valid("json");
await backupsService.reorderSchedules(body.scheduleIds);
await backupsService.reorderSchedules(body.scheduleShortIds);
return c.json<ReorderBackupSchedulesDto>({ success: true }, 200);
});

View file

@ -42,7 +42,7 @@ const backupScheduleSchema = type({
);
const scheduleMirrorSchema = type({
scheduleId: "number",
scheduleId: "string",
repositoryId: "string",
enabled: "boolean",
lastCopyAt: "number | null",
@ -125,7 +125,7 @@ export const getBackupScheduleForVolumeDto = describeRoute({
*/
export const createBackupScheduleBody = type({
name: "1 <= string <= 128",
volumeId: "number",
volumeId: "string | number",
repositoryId: "string",
enabled: "boolean",
cronExpression: "string",
@ -376,7 +376,7 @@ export const getMirrorCompatibilityDto = describeRoute({
* Reorder backup schedules
*/
export const reorderBackupSchedulesBody = type({
scheduleIds: "number[]",
scheduleShortIds: "string[]",
});
export type ReorderBackupSchedulesBody = typeof reorderBackupSchedulesBody.infer;
@ -388,7 +388,7 @@ export const reorderBackupSchedulesResponse = type({
export type ReorderBackupSchedulesDto = typeof reorderBackupSchedulesResponse.infer;
export const reorderBackupSchedulesDto = describeRoute({
description: "Reorder backup schedules by providing an array of schedule IDs in the desired order",
description: "Reorder backup schedules by providing an array of schedule short IDs in the desired order",
operationId: "reorderBackupSchedules",
tags: ["Backups"],
responses: {

View file

@ -89,7 +89,7 @@ const emitBackupStarted = (ctx: BackupContext, scheduleId: number) => {
serverEvents.emit("backup:started", {
organizationId: ctx.organizationId,
scheduleId,
scheduleId: ctx.schedule.shortId,
volumeName: ctx.volume.name,
repositoryName: ctx.repository.name,
});
@ -119,7 +119,7 @@ const runBackupOperation = async (ctx: BackupContext, signal: AbortSignal) => {
onProgress: (progress) => {
serverEvents.emit("backup:progress", {
organizationId: ctx.organizationId,
scheduleId: ctx.schedule.id,
scheduleId: ctx.schedule.shortId,
volumeName: ctx.volume.name,
repositoryName: ctx.repository.name,
...progress,
@ -173,7 +173,7 @@ const finalizeSuccessfulBackup = async (
serverEvents.emit("backup:completed", {
organizationId: ctx.organizationId,
scheduleId,
scheduleId: ctx.schedule.shortId,
volumeName: ctx.volume.name,
repositoryName: ctx.repository.name,
status: finalStatus,
@ -226,7 +226,7 @@ const handleBackupFailure = async (
serverEvents.emit("backup:completed", {
organizationId,
scheduleId,
scheduleId: ctx.schedule.shortId,
volumeName: ctx.volume.name,
repositoryName: ctx.repository.name,
status: "error",
@ -378,8 +378,8 @@ const copyToSingleMirror = async (
serverEvents.emit("mirror:started", {
organizationId,
scheduleId,
repositoryId: mirror.repositoryId,
scheduleId: schedule.shortId,
repositoryId: mirror.repository.shortId,
repositoryName: mirror.repository.name,
});
@ -417,8 +417,8 @@ const copyToSingleMirror = async (
serverEvents.emit("mirror:completed", {
organizationId,
scheduleId,
repositoryId: mirror.repositoryId,
scheduleId: schedule.shortId,
repositoryId: mirror.repository.shortId,
repositoryName: mirror.repository.name,
status: "success",
});
@ -434,8 +434,8 @@ const copyToSingleMirror = async (
serverEvents.emit("mirror:completed", {
organizationId,
scheduleId,
repositoryId: mirror.repositoryId,
scheduleId: schedule.shortId,
repositoryId: mirror.repository.shortId,
repositoryName: mirror.repository.name,
status: "error",
error: errorMessage,

View file

@ -94,7 +94,7 @@ const createSchedule = async (data: CreateBackupScheduleBody) => {
const volume = await db.query.volumesTable.findFirst({
where: {
AND: [{ id: data.volumeId }, { organizationId }],
AND: [{ OR: [{ id: Number(data.volumeId) }, { shortId: String(data.volumeId) }] }, { organizationId }],
},
});
@ -104,7 +104,7 @@ const createSchedule = async (data: CreateBackupScheduleBody) => {
const repository = await db.query.repositoriesTable.findFirst({
where: {
AND: [{ id: data.repositoryId }, { organizationId }],
AND: [{ OR: [{ id: data.repositoryId }, { shortId: data.repositoryId }] }, { organizationId }],
},
});
@ -118,8 +118,8 @@ const createSchedule = async (data: CreateBackupScheduleBody) => {
.insert(backupSchedulesTable)
.values({
name: data.name,
volumeId: data.volumeId,
repositoryId: data.repositoryId,
volumeId: volume.id,
repositoryId: repository.id,
enabled: data.enabled,
cronExpression: data.cronExpression,
retentionPolicy: data.retentionPolicy ?? null,
@ -140,11 +140,14 @@ const createSchedule = async (data: CreateBackupScheduleBody) => {
return newSchedule;
};
const updateSchedule = async (scheduleId: number, data: UpdateBackupScheduleBody) => {
const updateSchedule = async (scheduleIdOrShortId: number | string, data: UpdateBackupScheduleBody) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: {
AND: [{ id: scheduleId }, { organizationId }],
AND: [
{ OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: String(scheduleIdOrShortId) }] },
{ organizationId },
],
},
});
@ -159,7 +162,7 @@ const updateSchedule = async (scheduleId: number, data: UpdateBackupScheduleBody
if (data.name) {
const existingName = await db.query.backupSchedulesTable.findFirst({
where: {
AND: [{ name: data.name }, { NOT: { id: scheduleId } }, { organizationId }],
AND: [{ name: data.name }, { NOT: { id: schedule.id } }, { organizationId }],
},
});
@ -170,7 +173,7 @@ const updateSchedule = async (scheduleId: number, data: UpdateBackupScheduleBody
const repository = await db.query.repositoriesTable.findFirst({
where: {
AND: [{ id: data.repositoryId }, { organizationId }],
AND: [{ OR: [{ id: data.repositoryId }, { shortId: data.repositoryId }] }, { organizationId }],
},
});
@ -183,8 +186,8 @@ const updateSchedule = async (scheduleId: number, data: UpdateBackupScheduleBody
const [updated] = await db
.update(backupSchedulesTable)
.set({ ...data, nextBackupAt, updatedAt: Date.now() })
.where(and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId)))
.set({ ...data, repositoryId: repository.id, nextBackupAt, updatedAt: Date.now() })
.where(and(eq(backupSchedulesTable.id, schedule.id), eq(backupSchedulesTable.organizationId, organizationId)))
.returning();
if (!updated) {
@ -194,11 +197,14 @@ const updateSchedule = async (scheduleId: number, data: UpdateBackupScheduleBody
return updated;
};
const deleteSchedule = async (scheduleId: number) => {
const deleteSchedule = async (scheduleIdOrShortId: number | string) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: {
AND: [{ id: scheduleId }, { organizationId }],
AND: [
{ OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: String(scheduleIdOrShortId) }] },
{ organizationId },
],
},
});
@ -208,13 +214,26 @@ const deleteSchedule = async (scheduleId: number) => {
await db
.delete(backupSchedulesTable)
.where(and(eq(backupSchedulesTable.id, scheduleId), eq(backupSchedulesTable.organizationId, organizationId)));
.where(and(eq(backupSchedulesTable.id, schedule.id), eq(backupSchedulesTable.organizationId, organizationId)));
};
const getScheduleForVolume = async (volumeId: number) => {
const getScheduleForVolume = async (volumeIdOrShortId: number | string) => {
const organizationId = getOrganizationId();
const volume = await db.query.volumesTable.findFirst({
where: {
AND: [{ OR: [{ id: Number(volumeIdOrShortId) }, { shortId: String(volumeIdOrShortId) }] }, { organizationId }],
},
columns: { id: true },
});
if (!volume) {
return null;
}
const schedule = await db.query.backupSchedulesTable.findFirst({
where: { AND: [{ volumeId }, { organizationId }] },
where: {
AND: [{ volumeId: volume.id }, { organizationId }],
},
with: { volume: true, repository: true },
});
@ -225,11 +244,14 @@ const getScheduleForVolume = async (volumeId: number) => {
return schedule ?? null;
};
const getMirrors = async (scheduleId: number) => {
const getMirrors = async (scheduleIdOrShortId: number | string) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: {
AND: [{ id: scheduleId }, { organizationId }],
AND: [
{ OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: String(scheduleIdOrShortId) }] },
{ organizationId },
],
},
});
@ -239,18 +261,33 @@ const getMirrors = async (scheduleId: number) => {
const mirrors = await db.query.backupScheduleMirrorsTable.findMany({
where: {
scheduleId,
scheduleId: schedule.id,
},
with: { repository: true },
});
return mirrors;
return mirrors.map((mirror) => ({
id: mirror.id,
scheduleId: schedule.shortId,
repositoryId: mirror.repository.shortId,
enabled: mirror.enabled,
lastCopyAt: mirror.lastCopyAt,
lastCopyStatus: mirror.lastCopyStatus,
lastCopyError: mirror.lastCopyError,
createdAt: mirror.createdAt,
repository: mirror.repository,
}));
};
const updateMirrors = async (scheduleId: number, data: UpdateScheduleMirrorsBody) => {
const updateMirrors = async (scheduleIdOrShortId: number | string, data: UpdateScheduleMirrorsBody) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: { AND: [{ id: scheduleId }, { organizationId }] },
where: {
AND: [
{ OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: String(scheduleIdOrShortId) }] },
{ organizationId },
],
},
with: { repository: true },
});
@ -258,30 +295,36 @@ const updateMirrors = async (scheduleId: number, data: UpdateScheduleMirrorsBody
throw new NotFoundError("Backup schedule not found");
}
for (const mirror of data.mirrors) {
if (mirror.repositoryId === schedule.repositoryId) {
throw new BadRequestError("Cannot add the primary repository as a mirror");
}
const normalizedMirrors = await Promise.all(
data.mirrors.map(async (mirror) => {
const repo = await db.query.repositoriesTable.findFirst({
where: {
AND: [{ OR: [{ id: mirror.repositoryId }, { shortId: mirror.repositoryId }] }, { organizationId }],
},
});
const repo = await db.query.repositoriesTable.findFirst({
where: { AND: [{ id: mirror.repositoryId }, { organizationId }] },
});
if (!repo) {
throw new NotFoundError(`Repository ${mirror.repositoryId} not found`);
}
if (!repo) {
throw new NotFoundError(`Repository ${mirror.repositoryId} not found`);
}
if (repo.id === schedule.repositoryId) {
throw new BadRequestError("Cannot add the primary repository as a mirror");
}
const compatibility = await checkMirrorCompatibility(schedule.repository.config, repo.config, repo.id);
const compatibility = await checkMirrorCompatibility(schedule.repository.config, repo.config, repo.id);
if (!compatibility.compatible) {
throw new BadRequestError(
getIncompatibleMirrorError(repo.name, schedule.repository.config.backend, repo.config.backend),
);
}
}
if (!compatibility.compatible) {
throw new BadRequestError(
getIncompatibleMirrorError(repo.name, schedule.repository.config.backend, repo.config.backend),
);
}
return { repositoryId: repo.id, enabled: mirror.enabled };
}),
);
const existingMirrors = await db.query.backupScheduleMirrorsTable.findMany({
where: { scheduleId },
where: { scheduleId: schedule.id },
});
const existingMirrorsMap = new Map(
@ -291,14 +334,14 @@ const updateMirrors = async (scheduleId: number, data: UpdateScheduleMirrorsBody
]),
);
await db.delete(backupScheduleMirrorsTable).where(eq(backupScheduleMirrorsTable.scheduleId, scheduleId));
await db.delete(backupScheduleMirrorsTable).where(eq(backupScheduleMirrorsTable.scheduleId, schedule.id));
if (data.mirrors.length > 0) {
if (normalizedMirrors.length > 0) {
await db.insert(backupScheduleMirrorsTable).values(
data.mirrors.map((mirror) => {
normalizedMirrors.map((mirror) => {
const existing = existingMirrorsMap.get(mirror.repositoryId);
return {
scheduleId,
scheduleId: schedule.id,
repositoryId: mirror.repositoryId,
enabled: mirror.enabled,
lastCopyAt: existing?.lastCopyAt ?? null,
@ -309,13 +352,18 @@ const updateMirrors = async (scheduleId: number, data: UpdateScheduleMirrorsBody
);
}
return getMirrors(scheduleId);
return getMirrors(schedule.id);
};
const getMirrorCompatibility = async (scheduleId: number) => {
const getMirrorCompatibility = async (scheduleIdOrShortId: number | string) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: { AND: [{ id: scheduleId }, { organizationId }] },
where: {
AND: [
{ OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: String(scheduleIdOrShortId) }] },
{ organizationId },
],
},
with: { repository: true },
});
@ -327,29 +375,33 @@ const getMirrorCompatibility = async (scheduleId: number) => {
const repos = allRepositories.filter((repo) => repo.id !== schedule.repositoryId);
const compatibility = await Promise.all(
repos.map((repo) => checkMirrorCompatibility(schedule.repository.config, repo.config, repo.id)),
repos.map((repo) => checkMirrorCompatibility(schedule.repository.config, repo.config, repo.shortId)),
);
return compatibility;
};
const reorderSchedules = async (scheduleIds: number[]) => {
const reorderSchedules = async (scheduleShortIds: string[]) => {
const organizationId = getOrganizationId();
const uniqueIds = new Set(scheduleIds);
if (uniqueIds.size !== scheduleIds.length) {
const uniqueIds = new Set(scheduleShortIds);
if (uniqueIds.size !== scheduleShortIds.length) {
throw new BadRequestError("Duplicate schedule IDs in reorder request");
}
const existingSchedules = await db.query.backupSchedulesTable.findMany({
where: { organizationId },
columns: { id: true },
columns: { id: true, shortId: true },
});
const existingIds = new Set(existingSchedules.map((s) => s.id));
for (const id of scheduleIds) {
if (!existingIds.has(id)) {
throw new NotFoundError(`Backup schedule with ID ${id} not found`);
const shortIdToId = new Map(existingSchedules.map((s) => [s.shortId, s.id]));
const scheduleIds: number[] = [];
for (const shortId of scheduleShortIds) {
const id = shortIdToId.get(shortId);
if (id === undefined) {
throw new NotFoundError(`Backup schedule with short ID ${shortId} not found`);
}
scheduleIds.push(id);
}
db.transaction((tx) => {

View file

@ -186,7 +186,7 @@ describe("repositories updates", () => {
const { token, organizationId } = await createTestSession();
const repository = await createRepositoryRecord(organizationId);
const res = await app.request(`/api/v1/repositories/${repository.id}`, {
const res = await app.request(`/api/v1/repositories/${repository.shortId}`, {
method: "PATCH",
headers: {
...getAuthHeaders(token),
@ -212,7 +212,7 @@ describe("repositories updates", () => {
const { token, organizationId } = await createTestSession();
const repository = await createRepositoryRecord(organizationId);
const res = await app.request(`/api/v1/repositories/${repository.id}`, {
const res = await app.request(`/api/v1/repositories/${repository.shortId}`, {
method: "PATCH",
headers: {
...getAuthHeaders(token),

View file

@ -116,6 +116,7 @@ const saveDoctorResults = async (repositoryId: string, steps: DoctorStep[], fina
export const executeDoctor = async (
repositoryId: string,
repositoryShortId: string,
repositoryConfig: RepositoryConfig,
repositoryName: string,
signal: AbortSignal,
@ -153,7 +154,7 @@ export const executeDoctor = async (
serverEvents.emit("doctor:completed", {
organizationId,
repositoryId,
repositoryId: repositoryShortId,
repositoryName,
success: steps.every((s) => s.success),
steps,
@ -179,7 +180,7 @@ export const executeDoctor = async (
serverEvents.emit("doctor:cancelled", {
organizationId,
repositoryId,
repositoryId: repositoryShortId,
repositoryName,
error: toMessage(error),
});
@ -192,7 +193,7 @@ export const executeDoctor = async (
steps.push({ step: "doctor", success: false, output: null, error: toMessage(error) });
serverEvents.emit("doctor:completed", {
organizationId,
repositoryId,
repositoryId: repositoryShortId,
repositoryName,
success: false,
steps,

View file

@ -79,25 +79,25 @@ export const repositoriesController = new Hono()
return c.json(remotes);
})
.get("/:id", getRepositoryDto, async (c) => {
const { id } = c.req.param();
const res = await repositoriesService.getRepository(id);
.get("/:shortId", getRepositoryDto, async (c) => {
const { shortId } = c.req.param();
const res = await repositoriesService.getRepository(shortId);
return c.json<GetRepositoryDto>(res.repository, 200);
})
.delete("/:id", deleteRepositoryDto, async (c) => {
const { id } = c.req.param();
await repositoriesService.deleteRepository(id);
.delete("/:shortId", deleteRepositoryDto, async (c) => {
const { shortId } = c.req.param();
await repositoriesService.deleteRepository(shortId);
return c.json<DeleteRepositoryDto>({ message: "Repository deleted" }, 200);
})
.get("/:id/snapshots", listSnapshotsDto, validator("query", listSnapshotsFilters), async (c) => {
const { id } = c.req.param();
.get("/:shortId/snapshots", listSnapshotsDto, validator("query", listSnapshotsFilters), async (c) => {
const { shortId } = c.req.param();
const { backupId } = c.req.valid("query");
const [res, retentionCategories] = await Promise.all([
repositoriesService.listSnapshots(id, backupId),
repositoriesService.getRetentionCategories(id, backupId),
repositoriesService.listSnapshots(shortId, backupId),
repositoriesService.getRetentionCategories(shortId, backupId),
]);
const snapshots = res.map((snapshot) => {
@ -119,15 +119,15 @@ export const repositoriesController = new Hono()
return c.json<ListSnapshotsDto>(snapshots, 200);
})
.post("/:id/snapshots/refresh", refreshSnapshotsDto, async (c) => {
const { id } = c.req.param();
const result = await repositoriesService.refreshSnapshots(id);
.post("/:shortId/snapshots/refresh", refreshSnapshotsDto, async (c) => {
const { shortId } = c.req.param();
const result = await repositoriesService.refreshSnapshots(shortId);
return c.json<RefreshSnapshotsDto>(result, 200);
})
.get("/:id/snapshots/:snapshotId", getSnapshotDetailsDto, async (c) => {
const { id, snapshotId } = c.req.param();
const snapshot = await repositoriesService.getSnapshotDetails(id, snapshotId);
.get("/:shortId/snapshots/:snapshotId", getSnapshotDetailsDto, async (c) => {
const { shortId, snapshotId } = c.req.param();
const snapshot = await repositoriesService.getSnapshotDetails(shortId, snapshotId);
const duration = getSnapshotDuration(snapshot.summary);
@ -146,11 +146,11 @@ export const repositoriesController = new Hono()
return c.json<GetSnapshotDetailsDto>(response, 200);
})
.get(
"/:id/snapshots/:snapshotId/files",
"/:shortId/snapshots/:snapshotId/files",
listSnapshotFilesDto,
validator("query", listSnapshotFilesQuery),
async (c) => {
const { id, snapshotId } = c.req.param();
const { shortId, snapshotId } = c.req.param();
const { path, ...query } = c.req.valid("query");
const decodedPath = path ? decodeURIComponent(path) : undefined;
@ -158,76 +158,76 @@ export const repositoriesController = new Hono()
const offset = Math.max(0, Number.parseInt(query.offset ?? "0", 10) || 0);
const limit = Math.min(1000, Math.max(1, Number.parseInt(query.limit ?? "500", 10) || 500));
const result = await repositoriesService.listSnapshotFiles(id, snapshotId, decodedPath, { offset, limit });
const result = await repositoriesService.listSnapshotFiles(shortId, snapshotId, decodedPath, { offset, limit });
c.header("Cache-Control", "max-age=300, stale-while-revalidate=600");
return c.json<ListSnapshotFilesDto>(result, 200);
},
)
.post("/:id/restore", restoreSnapshotDto, validator("json", restoreSnapshotBody), async (c) => {
const { id } = c.req.param();
.post("/:shortId/restore", restoreSnapshotDto, validator("json", restoreSnapshotBody), async (c) => {
const { shortId } = c.req.param();
const { snapshotId, ...options } = c.req.valid("json");
const result = await repositoriesService.restoreSnapshot(id, snapshotId, options);
const result = await repositoriesService.restoreSnapshot(shortId, snapshotId, options);
return c.json<RestoreSnapshotDto>(result, 200);
})
.post("/:id/doctor", startDoctorDto, async (c) => {
const { id } = c.req.param();
.post("/:shortId/doctor", startDoctorDto, async (c) => {
const { shortId } = c.req.param();
const result = await repositoriesService.startDoctor(id);
const result = await repositoriesService.startDoctor(shortId);
return c.json<StartDoctorDto>(result, 202);
})
.delete("/:id/doctor", cancelDoctorDto, async (c) => {
const { id } = c.req.param();
.delete("/:shortId/doctor", cancelDoctorDto, async (c) => {
const { shortId } = c.req.param();
const result = await repositoriesService.cancelDoctor(id);
const result = await repositoriesService.cancelDoctor(shortId);
return c.json<CancelDoctorDto>(result, 200);
})
.post("/:id/unlock", unlockRepositoryDto, async (c) => {
const { id } = c.req.param();
.post("/:shortId/unlock", unlockRepositoryDto, async (c) => {
const { shortId } = c.req.param();
const result = await repositoriesService.unlockRepository(id);
const result = await repositoriesService.unlockRepository(shortId);
return c.json<UnlockRepositoryDto>(result, 200);
})
.delete("/:id/snapshots/:snapshotId", deleteSnapshotDto, async (c) => {
const { id, snapshotId } = c.req.param();
await repositoriesService.deleteSnapshot(id, snapshotId);
.delete("/:shortId/snapshots/:snapshotId", deleteSnapshotDto, async (c) => {
const { shortId, snapshotId } = c.req.param();
await repositoriesService.deleteSnapshot(shortId, snapshotId);
return c.json<DeleteSnapshotDto>({ message: "Snapshot deleted" }, 200);
})
.delete("/:id/snapshots", deleteSnapshotsDto, validator("json", deleteSnapshotsBody), async (c) => {
const { id } = c.req.param();
.delete("/:shortId/snapshots", deleteSnapshotsDto, validator("json", deleteSnapshotsBody), async (c) => {
const { shortId } = c.req.param();
const { snapshotIds } = c.req.valid("json");
await repositoriesService.deleteSnapshots(id, snapshotIds);
await repositoriesService.deleteSnapshots(shortId, snapshotIds);
return c.json<DeleteSnapshotsResponseDto>({ message: "Snapshots deleted" }, 200);
})
.post("/:id/snapshots/tag", tagSnapshotsDto, validator("json", tagSnapshotsBody), async (c) => {
const { id } = c.req.param();
.post("/:shortId/snapshots/tag", tagSnapshotsDto, validator("json", tagSnapshotsBody), async (c) => {
const { shortId } = c.req.param();
const { snapshotIds, ...tags } = c.req.valid("json");
await repositoriesService.tagSnapshots(id, snapshotIds, tags);
await repositoriesService.tagSnapshots(shortId, snapshotIds, tags);
return c.json<TagSnapshotsResponseDto>({ message: "Snapshots tagged" }, 200);
})
.patch("/:id", updateRepositoryDto, validator("json", updateRepositoryBody), async (c) => {
const { id } = c.req.param();
.patch("/:shortId", updateRepositoryDto, validator("json", updateRepositoryBody), async (c) => {
const { shortId } = c.req.param();
const body = c.req.valid("json");
const res = await repositoriesService.updateRepository(id, body);
const res = await repositoriesService.updateRepository(shortId, body);
return c.json<UpdateRepositoryDto>(res.repository, 200);
})
.post(
"/:id/exec",
"/:shortId/exec",
requireDevPanel,
requireOrgAdmin,
devPanelExecDto,
validator("json", devPanelExecBody),
async (c) => {
const { id } = c.req.param();
const { shortId } = c.req.param();
const body = c.req.valid("json");
return streamSSE(c, async (stream) => {
@ -240,7 +240,7 @@ export const repositoriesController = new Hono()
try {
const result = await repositoriesService.execResticCommand(
id,
shortId,
body.command,
body.args,
async (line) => sendSSE("output", { type: "stdout", line }),

View file

@ -29,11 +29,11 @@ import { findCommonAncestor } from "~/utils/common-ancestor";
const runningDoctors = new Map<string, AbortController>();
const findRepository = async (idOrShortId: string) => {
const findRepository = async (shortId: string) => {
const organizationId = getOrganizationId();
return await db.query.repositoriesTable.findFirst({
where: {
AND: [{ OR: [{ id: idOrShortId }, { shortId: idOrShortId }] }, { organizationId }],
AND: [{ shortId }, { organizationId }],
},
});
};
@ -182,8 +182,8 @@ const createRepository = async (name: string, config: RepositoryConfig, compress
throw new InternalServerError(`Failed to initialize repository: ${errorMessage}`);
};
const getRepository = async (id: string) => {
const repository = await findRepository(id);
const getRepository = async (shortId: string) => {
const repository = await findRepository(shortId);
if (!repository) {
throw new NotFoundError("Repository not found");
@ -192,8 +192,8 @@ const getRepository = async (id: string) => {
return { repository };
};
const deleteRepository = async (id: string) => {
const repository = await findRepository(id);
const deleteRepository = async (shortId: string) => {
const repository = await findRepository(shortId);
if (!repository) {
throw new NotFoundError("Repository not found");
@ -214,14 +214,14 @@ const deleteRepository = async (id: string) => {
/**
* List snapshots for a given repository
* If backupId is provided, filter snapshots by that backup ID (tag)
* @param id Repository ID
* @param shortId Repository short ID
* @param backupId Optional backup ID to filter snapshots for a specific backup schedule
*
* @returns List of snapshots
*/
const listSnapshots = async (id: string, backupId?: string) => {
const listSnapshots = async (shortId: string, backupId?: string) => {
const organizationId = getOrganizationId();
const repository = await findRepository(id);
const repository = await findRepository(shortId);
if (!repository) {
throw new NotFoundError("Repository not found");
@ -252,13 +252,13 @@ const listSnapshots = async (id: string, backupId?: string) => {
};
const listSnapshotFiles = async (
id: string,
shortId: string,
snapshotId: string,
path?: string,
options?: { offset?: number; limit?: number },
) => {
const organizationId = getOrganizationId();
const repository = await findRepository(id);
const repository = await findRepository(shortId);
if (!repository) {
throw new NotFoundError("Repository not found");
@ -317,7 +317,7 @@ const listSnapshotFiles = async (
};
const restoreSnapshot = async (
id: string,
shortId: string,
snapshotId: string,
options?: {
include?: string[];
@ -329,7 +329,7 @@ const restoreSnapshot = async (
},
) => {
const organizationId = getOrganizationId();
const repository = await findRepository(id);
const repository = await findRepository(shortId);
if (!repository) {
throw new NotFoundError("Repository not found");
@ -337,14 +337,14 @@ const restoreSnapshot = async (
const target = options?.targetPath || "/";
const { paths } = await getSnapshotDetails(repository.id, snapshotId);
const { paths } = await getSnapshotDetails(repository.shortId, snapshotId);
const basePath = findCommonAncestor(paths);
const releaseLock = await repoMutex.acquireShared(repository.id, `restore:${snapshotId}`);
try {
serverEvents.emit("restore:started", {
organizationId,
repositoryId: repository.id,
repositoryId: repository.shortId,
snapshotId,
});
@ -355,7 +355,7 @@ const restoreSnapshot = async (
onProgress: (progress) => {
serverEvents.emit("restore:progress", {
organizationId,
repositoryId: repository.id,
repositoryId: repository.shortId,
snapshotId,
...progress,
});
@ -364,7 +364,7 @@ const restoreSnapshot = async (
serverEvents.emit("restore:completed", {
organizationId,
repositoryId: repository.id,
repositoryId: repository.shortId,
snapshotId,
status: "success",
});
@ -378,7 +378,7 @@ const restoreSnapshot = async (
} catch (error) {
serverEvents.emit("restore:completed", {
organizationId,
repositoryId: repository.id,
repositoryId: repository.shortId,
snapshotId,
status: "error",
error: toMessage(error),
@ -389,9 +389,9 @@ const restoreSnapshot = async (
}
};
const getSnapshotDetails = async (id: string, snapshotId: string) => {
const getSnapshotDetails = async (shortId: string, snapshotId: string) => {
const organizationId = getOrganizationId();
const repository = await findRepository(id);
const repository = await findRepository(shortId);
if (!repository) {
throw new NotFoundError("Repository not found");
@ -413,7 +413,7 @@ const getSnapshotDetails = async (id: string, snapshotId: string) => {
const snapshot = snapshots.find((snap) => snap.id === snapshotId || snap.short_id === snapshotId);
if (!snapshot) {
void refreshSnapshots(id).catch(() => {});
void refreshSnapshots(shortId).catch(() => {});
throw new NotFoundError("Snapshot not found");
}
@ -450,8 +450,8 @@ const checkHealth = async (repositoryId: string) => {
}
};
const startDoctor = async (id: string) => {
const repository = await findRepository(id);
const startDoctor = async (shortId: string) => {
const repository = await findRepository(shortId);
if (!repository) {
throw new NotFoundError("Repository not found");
@ -468,7 +468,7 @@ const startDoctor = async (id: string) => {
serverEvents.emit("doctor:started", {
organizationId: repository.organizationId,
repositoryId: repository.id,
repositoryId: repository.shortId,
repositoryName: repository.name,
});
@ -478,7 +478,7 @@ const startDoctor = async (id: string) => {
throw error;
}
executeDoctor(repository.id, repository.config, repository.name, abortController.signal)
executeDoctor(repository.id, repository.shortId, repository.config, repository.name, abortController.signal)
.catch((error) => {
logger.error(`Doctor background task failed: ${toMessage(error)}`);
})
@ -486,11 +486,11 @@ const startDoctor = async (id: string) => {
runningDoctors.delete(repository.id);
});
return { message: "Doctor operation started", repositoryId: repository.id };
return { message: "Doctor operation started", repositoryId: repository.shortId };
};
const cancelDoctor = async (id: string) => {
const repository = await findRepository(id);
const cancelDoctor = async (shortId: string) => {
const repository = await findRepository(shortId);
if (!repository) {
throw new NotFoundError("Repository not found");
@ -509,16 +509,16 @@ const cancelDoctor = async (id: string) => {
serverEvents.emit("doctor:cancelled", {
organizationId: repository.organizationId,
repositoryId: repository.id,
repositoryId: repository.shortId,
repositoryName: repository.name,
});
return { message: "Doctor operation cancelled" };
};
const deleteSnapshot = async (id: string, snapshotId: string) => {
const deleteSnapshot = async (shortId: string, snapshotId: string) => {
const organizationId = getOrganizationId();
const repository = await findRepository(id);
const repository = await findRepository(shortId);
if (!repository) {
throw new NotFoundError("Repository not found");
@ -534,9 +534,9 @@ const deleteSnapshot = async (id: string, snapshotId: string) => {
}
};
const deleteSnapshots = async (id: string, snapshotIds: string[]) => {
const deleteSnapshots = async (shortId: string, snapshotIds: string[]) => {
const organizationId = getOrganizationId();
const repository = await findRepository(id);
const repository = await findRepository(shortId);
if (!repository) {
throw new NotFoundError("Repository not found");
@ -555,12 +555,12 @@ const deleteSnapshots = async (id: string, snapshotIds: string[]) => {
};
const tagSnapshots = async (
id: string,
shortId: string,
snapshotIds: string[],
tags: { add?: string[]; remove?: string[]; set?: string[] },
) => {
const organizationId = getOrganizationId();
const repository = await findRepository(id);
const repository = await findRepository(shortId);
if (!repository) {
throw new NotFoundError("Repository not found");
@ -578,9 +578,9 @@ const tagSnapshots = async (
}
};
const refreshSnapshots = async (id: string) => {
const refreshSnapshots = async (shortId: string) => {
const organizationId = getOrganizationId();
const repository = await findRepository(id);
const repository = await findRepository(shortId);
if (!repository) {
throw new NotFoundError("Repository not found");
@ -604,8 +604,8 @@ const refreshSnapshots = async (id: string) => {
}
};
const updateRepository = async (id: string, updates: UpdateRepositoryBody) => {
const existing = await findRepository(id);
const updateRepository = async (shortId: string, updates: UpdateRepositoryBody) => {
const existing = await findRepository(shortId);
if (!existing) {
throw new NotFoundError("Repository not found");
@ -676,9 +676,9 @@ const updateRepository = async (id: string, updates: UpdateRepositoryBody) => {
return { repository: updated };
};
const unlockRepository = async (id: string) => {
const unlockRepository = async (shortId: string) => {
const organizationId = getOrganizationId();
const repository = await findRepository(id);
const repository = await findRepository(shortId);
if (!repository) {
throw new NotFoundError("Repository not found");
@ -694,7 +694,7 @@ const unlockRepository = async (id: string) => {
};
const execResticCommand = async (
id: string,
shortId: string,
command: string,
args: string[] | undefined,
onStdout: (line: string) => void,
@ -702,7 +702,7 @@ const execResticCommand = async (
signal?: AbortSignal,
) => {
const organizationId = getOrganizationId();
const repository = await findRepository(id);
const repository = await findRepository(shortId);
if (!repository) {
throw new NotFoundError("Repository not found");

View file

@ -51,15 +51,15 @@ export const volumeController = new Hono()
return c.json(result, 200);
})
.delete("/:id", deleteVolumeDto, async (c) => {
const { id } = c.req.param();
await volumeService.deleteVolume(id);
.delete("/:shortId", deleteVolumeDto, async (c) => {
const { shortId } = c.req.param();
await volumeService.deleteVolume(shortId);
return c.json({ message: "Volume deleted" }, 200);
})
.get("/:id", getVolumeDto, async (c) => {
const { id } = c.req.param();
const res = await volumeService.getVolume(id);
.get("/:shortId", getVolumeDto, async (c) => {
const { shortId } = c.req.param();
const res = await volumeService.getVolume(shortId);
const response = {
volume: {
@ -75,10 +75,10 @@ export const volumeController = new Hono()
return c.json<GetVolumeDto>(response, 200);
})
.put("/:id", updateVolumeDto, validator("json", updateVolumeBody), async (c) => {
const { id } = c.req.param();
.put("/:shortId", updateVolumeDto, validator("json", updateVolumeBody), async (c) => {
const { shortId } = c.req.param();
const body = c.req.valid("json");
const res = await volumeService.updateVolume(id, body);
const res = await volumeService.updateVolume(shortId, body);
const response = {
...res.volume,
@ -87,32 +87,32 @@ export const volumeController = new Hono()
return c.json<UpdateVolumeDto>(response, 200);
})
.post("/:id/mount", mountVolumeDto, async (c) => {
const { id } = c.req.param();
const { error, status } = await volumeService.mountVolume(id);
.post("/:shortId/mount", mountVolumeDto, async (c) => {
const { shortId } = c.req.param();
const { error, status } = await volumeService.mountVolume(shortId);
return c.json({ error, status }, error ? 500 : 200);
})
.post("/:id/unmount", unmountVolumeDto, async (c) => {
const { id } = c.req.param();
const { error, status } = await volumeService.unmountVolume(id);
.post("/:shortId/unmount", unmountVolumeDto, async (c) => {
const { shortId } = c.req.param();
const { error, status } = await volumeService.unmountVolume(shortId);
return c.json({ error, status }, error ? 500 : 200);
})
.post("/:id/health-check", healthCheckDto, async (c) => {
const { id } = c.req.param();
const { error, status } = await volumeService.checkHealth(id);
.post("/:shortId/health-check", healthCheckDto, async (c) => {
const { shortId } = c.req.param();
const { error, status } = await volumeService.checkHealth(shortId);
return c.json({ error, status }, 200);
})
.get("/:id/files", validator("query", listFilesQuery), listFilesDto, async (c) => {
const { id } = c.req.param();
.get("/:shortId/files", validator("query", listFilesQuery), listFilesDto, async (c) => {
const { shortId } = c.req.param();
const { path, ...query } = c.req.valid("query");
const offset = Math.max(0, Number.parseInt(query.offset ?? "0", 10) || 0);
const limit = Math.min(1000, Math.max(1, Number.parseInt(query.limit ?? "500", 10) || 500));
const result = await volumeService.listFiles(id, path, offset, limit);
const result = await volumeService.listFiles(shortId, path, offset, limit);
const response = {
files: result.files,

View file

@ -52,14 +52,11 @@ const listVolumes = async () => {
return volumes;
};
const findVolume = async (idOrShortId: string | number) => {
const findVolume = async (identifier: string | number) => {
const organizationId = getOrganizationId();
return await db.query.volumesTable.findFirst({
where: {
AND: [
{ OR: [{ id: Number(idOrShortId) }, { shortId: String(idOrShortId) }] },
{ organizationId: organizationId },
],
AND: [{ OR: [{ id: Number(identifier) }, { shortId: String(identifier) }] }, { organizationId: organizationId }],
},
});
};
@ -101,9 +98,9 @@ const createVolume = async (name: string, backendConfig: BackendConfig) => {
return { volume: created, status: 201 };
};
const deleteVolume = async (idOrShortId: string | number) => {
const deleteVolume = async (identifier: string | number) => {
const organizationId = getOrganizationId();
const volume = await findVolume(idOrShortId);
const volume = await findVolume(identifier);
if (!volume) {
throw new NotFoundError("Volume not found");
@ -116,9 +113,9 @@ const deleteVolume = async (idOrShortId: string | number) => {
.where(and(eq(volumesTable.id, volume.id), eq(volumesTable.organizationId, organizationId)));
};
const mountVolume = async (idOrShortId: string | number) => {
const mountVolume = async (identifier: string | number) => {
const organizationId = getOrganizationId();
const volume = await findVolume(idOrShortId);
const volume = await findVolume(identifier);
if (!volume) {
throw new NotFoundError("Volume not found");
@ -139,9 +136,9 @@ const mountVolume = async (idOrShortId: string | number) => {
return { error, status };
};
const unmountVolume = async (idOrShortId: string | number) => {
const unmountVolume = async (identifier: string | number) => {
const organizationId = getOrganizationId();
const volume = await findVolume(idOrShortId);
const volume = await findVolume(identifier);
if (!volume) {
throw new NotFoundError("Volume not found");
@ -162,8 +159,8 @@ const unmountVolume = async (idOrShortId: string | number) => {
return { error, status };
};
const getVolume = async (idOrShortId: string | number) => {
const volume = await findVolume(idOrShortId);
const getVolume = async (identifier: string | number) => {
const volume = await findVolume(identifier);
if (!volume) {
throw new NotFoundError("Volume not found");
@ -180,9 +177,9 @@ const getVolume = async (idOrShortId: string | number) => {
return { volume, statfs };
};
const updateVolume = async (idOrShortId: string | number, volumeData: UpdateVolumeBody) => {
const updateVolume = async (identifier: string | number, volumeData: UpdateVolumeBody) => {
const organizationId = getOrganizationId();
const existing = await findVolume(idOrShortId);
const existing = await findVolume(identifier);
if (!existing) {
throw new NotFoundError("Volume not found");
@ -273,9 +270,9 @@ const testConnection = async (backendConfig: BackendConfig) => {
};
};
const checkHealth = async (idOrShortId: string | number) => {
const checkHealth = async (identifier: string | number) => {
const organizationId = getOrganizationId();
const volume = await findVolume(idOrShortId);
const volume = await findVolume(identifier);
if (!volume) {
throw new NotFoundError("Volume not found");
@ -300,12 +297,12 @@ const DEFAULT_PAGE_SIZE = 500;
const MAX_PAGE_SIZE = 500;
const listFiles = async (
idOrShortId: string | number,
identifier: string | number,
subPath?: string,
offset: number = 0,
limit: number = DEFAULT_PAGE_SIZE,
) => {
const volume = await findVolume(idOrShortId);
const volume = await findVolume(identifier);
if (!volume) {
throw new NotFoundError("Volume not found");