refactor: always use short id in api calls

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,8 +7,8 @@ import { Link } from "@tanstack/react-router";
export const BackupCard = ({ schedule }: { schedule: BackupSchedule }) => { export const BackupCard = ({ schedule }: { schedule: BackupSchedule }) => {
return ( return (
<Link key={schedule.id} to="/backups/$backupId" params={{ backupId: schedule.id.toString() }}> <Link key={schedule.shortId} to="/backups/$backupId" params={{ backupId: schedule.shortId }}>
<Card key={schedule.id} className="flex flex-col h-full"> <Card key={schedule.shortId} className="flex flex-col h-full">
<CardHeader className="pb-3 overflow-hidden"> <CardHeader className="pb-3 overflow-hidden">
<div className="flex items-center justify-between gap-2 w-full"> <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"> <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"; import { formatBytes } from "~/utils/format-bytes";
type Props = { type Props = {
scheduleId: number; scheduleShortId: string;
}; };
export const BackupProgressCard = ({ scheduleId }: Props) => { export const BackupProgressCard = ({ scheduleShortId }: Props) => {
const { addEventListener } = useServerEvents(); const { addEventListener } = useServerEvents();
const [progress, setProgress] = useState<BackupProgressEvent | null>(null); const [progress, setProgress] = useState<BackupProgressEvent | null>(null);
useEffect(() => { useEffect(() => {
const unsubscribe = addEventListener("backup:progress", (progressData) => { const unsubscribe = addEventListener("backup:progress", (progressData) => {
if (progressData.scheduleId === scheduleId) { if (progressData.scheduleId === scheduleShortId) {
setProgress(progressData); setProgress(progressData);
} }
}); });
const unsubscribeComplete = addEventListener("backup:completed", (completedData) => { const unsubscribeComplete = addEventListener("backup:completed", (completedData) => {
if (completedData.scheduleId === scheduleId) { if (completedData.scheduleId === scheduleShortId) {
setProgress(null); setProgress(null);
} }
}); });
@ -31,7 +31,7 @@ export const BackupProgressCard = ({ scheduleId }: Props) => {
unsubscribe(); unsubscribe();
unsubscribeComplete(); unsubscribeComplete();
}; };
}, [addEventListener, scheduleId]); }, [addEventListener, scheduleShortId]);
const percentDone = progress ? Math.round(progress.percent_done * 100) : 0; const percentDone = progress ? Math.round(progress.percent_done * 100) : 0;
const currentFile = progress?.current_files[0] || ""; const currentFile = progress?.current_files[0] || "";

View file

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

View file

@ -34,7 +34,9 @@ export const SummarySection = ({ volume, frequency, formValues }: SummarySection
</div> </div>
<div> <div>
<p className="text-xs uppercase text-muted-foreground">Repository</p> <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> </div>
{(formValues.includePatterns && formValues.includePatterns.length > 0) || formValues.includePatternsText ? ( {(formValues.includePatterns && formValues.includePatterns.length > 0) || formValues.includePatternsText ? (
<div> <div>

View file

@ -2,9 +2,7 @@ import type { BackupSchedule } from "~/client/lib/types";
import { cronToFormValues } from "../../lib/cron-utils"; import { cronToFormValues } from "../../lib/cron-utils";
import type { InternalFormValues } from "./types"; import type { InternalFormValues } from "./types";
export const backupScheduleToFormValues = ( export const backupScheduleToFormValues = (schedule?: BackupSchedule): InternalFormValues | undefined => {
schedule?: BackupSchedule,
): InternalFormValues | undefined => {
if (!schedule) { if (!schedule) {
return undefined; return undefined;
} }
@ -18,7 +16,7 @@ export const backupScheduleToFormValues = (
return { return {
name: schedule.name, name: schedule.name,
repositoryId: schedule.repositoryId, repositoryId: schedule.repository.shortId,
includePatterns: fileBrowserPaths.length > 0 ? fileBrowserPaths : undefined, includePatterns: fileBrowserPaths.length > 0 ? fileBrowserPaths : undefined,
includePatternsText: textPatterns.length > 0 ? textPatterns.join("\n") : undefined, includePatternsText: textPatterns.length > 0 ? textPatterns.join("\n") : undefined,
excludePatternsText: schedule.excludePatterns?.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"; import { Link } from "@tanstack/react-router";
type Props = { type Props = {
scheduleId: number; scheduleShortId: string;
primaryRepositoryId: string; primaryRepositoryId: string;
repositories: Repository[]; repositories: Repository[];
initialData: GetScheduleMirrorsResponse; 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 [assignments, setAssignments] = useState<Map<string, MirrorAssignment>>(() => buildAssignments(initialData));
const [hasChanges, setHasChanges] = useState(false); const [hasChanges, setHasChanges] = useState(false);
const [isAddingNew, setIsAddingNew] = useState(false); const [isAddingNew, setIsAddingNew] = useState(false);
const { addEventListener } = useServerEvents(); const { addEventListener } = useServerEvents();
const { data: currentMirrors } = useSuspenseQuery({ const { data: currentMirrors } = useSuspenseQuery({
...getScheduleMirrorsOptions({ path: { scheduleId: scheduleId.toString() } }), ...getScheduleMirrorsOptions({ path: { shortId: scheduleShortId } }),
}); });
useEffect(() => { useEffect(() => {
@ -72,7 +72,7 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
}, [currentMirrors, hasChanges]); }, [currentMirrors, hasChanges]);
const { data: compatibility } = useQuery({ const { data: compatibility } = useQuery({
...getMirrorCompatibilityOptions({ path: { scheduleId: scheduleId.toString() } }), ...getMirrorCompatibilityOptions({ path: { shortId: scheduleShortId } }),
}); });
const updateMirrors = useMutation({ const updateMirrors = useMutation({
@ -100,7 +100,7 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
useEffect(() => { useEffect(() => {
const unsubscribeStarted = addEventListener("mirror:started", (event) => { const unsubscribeStarted = addEventListener("mirror:started", (event) => {
if (event.scheduleId !== scheduleId) return; if (event.scheduleId !== scheduleShortId) return;
setAssignments((prev) => { setAssignments((prev) => {
const next = new Map(prev); const next = new Map(prev);
const existing = next.get(event.repositoryId); const existing = next.get(event.repositoryId);
@ -111,7 +111,7 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
}); });
const unsubscribeCompleted = addEventListener("mirror:completed", (event) => { const unsubscribeCompleted = addEventListener("mirror:completed", (event) => {
if (event.scheduleId !== scheduleId) return; if (event.scheduleId !== scheduleShortId) return;
setAssignments((prev) => { setAssignments((prev) => {
const next = new Map(prev); const next = new Map(prev);
const existing = next.get(event.repositoryId); const existing = next.get(event.repositoryId);
@ -130,7 +130,7 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
unsubscribeStarted(); unsubscribeStarted();
unsubscribeCompleted(); unsubscribeCompleted();
}; };
}, [addEventListener, scheduleId]); }, [addEventListener, scheduleShortId]);
const addRepository = (repositoryId: string) => { const addRepository = (repositoryId: string) => {
const newAssignments = new Map(assignments); const newAssignments = new Map(assignments);
@ -174,7 +174,7 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
enabled: a.enabled, enabled: a.enabled,
})); }));
updateMirrors.mutate({ updateMirrors.mutate({
path: { scheduleId: scheduleId.toString() }, path: { shortId: scheduleShortId },
body: { body: {
mirrors: mirrorsList, mirrors: mirrorsList,
}, },
@ -188,18 +188,18 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
const selectableRepositories = const selectableRepositories =
repositories?.filter((r) => { repositories?.filter((r) => {
if (r.id === primaryRepositoryId) return false; if (r.shortId === primaryRepositoryId) return false;
if (assignments.has(r.id)) return false; if (assignments.has(r.shortId)) return false;
return true; return true;
}) || []; }) || [];
const hasAvailableRepositories = selectableRepositories.some((r) => { const hasAvailableRepositories = selectableRepositories.some((r) => {
const compat = compatibilityMap.get(r.id); const compat = compatibilityMap.get(r.shortId);
return compat?.compatible !== false; return compat?.compatible !== false;
}); });
const assignedRepositories = Array.from(assignments.keys()) 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); .filter((r) => r !== undefined);
const getStatusVariant = (status: string | null) => { const getStatusVariant = (status: string | null) => {
@ -262,13 +262,13 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{selectableRepositories.map((repository) => { {selectableRepositories.map((repository) => {
const compat = compatibilityMap.get(repository.id); const compat = compatibilityMap.get(repository.shortId);
return ( return (
<Tooltip key={repository.id}> <Tooltip key={repository.shortId}>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div> <div>
<SelectItem value={repository.id} disabled={!compat?.compatible}> <SelectItem value={repository.shortId} disabled={!compat?.compatible}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<RepositoryIcon backend={repository.type} className="h-4 w-4" /> <RepositoryIcon backend={repository.type} className="h-4 w-4" />
<span>{repository.name}</span> <span>{repository.name}</span>
@ -322,16 +322,16 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{assignedRepositories.map((repository) => { {assignedRepositories.map((repository) => {
const assignment = assignments.get(repository.id); const assignment = assignments.get(repository.shortId);
if (!assignment) return null; if (!assignment) return null;
return ( return (
<TableRow key={repository.id}> <TableRow key={repository.shortId}>
<TableCell> <TableCell>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Link <Link
to="/repositories/$repositoryId" to="/repositories/$repositoryId"
params={{ repositoryId: repository.id }} params={{ repositoryId: repository.shortId }}
className="hover:underline flex items-center gap-2" className="hover:underline flex items-center gap-2"
> >
<RepositoryIcon backend={repository.type} className="h-4 w-4" /> <RepositoryIcon backend={repository.type} className="h-4 w-4" />
@ -346,7 +346,7 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
<Switch <Switch
className="align-middle" className="align-middle"
checked={assignment.enabled} checked={assignment.enabled}
onCheckedChange={() => toggleEnabled(repository.id)} onCheckedChange={() => toggleEnabled(repository.shortId)}
/> />
</TableCell> </TableCell>
<TableCell> <TableCell>
@ -363,7 +363,7 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
onClick={() => removeRepository(repository.id)} onClick={() => removeRepository(repository.shortId)}
className="h-8 w-8 text-muted-foreground hover:text-destructive align-baseline" className="h-8 w-8 text-muted-foreground hover:text-destructive align-baseline"
> >
<Trash2 className="h-4 w-4" /> <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"; import type { GetScheduleNotificationsResponse } from "~/client/api-client";
type Props = { type Props = {
scheduleId: number; scheduleShortId: string;
destinations: NotificationDestination[]; destinations: NotificationDestination[];
initialData: GetScheduleNotificationsResponse; initialData: GetScheduleNotificationsResponse;
}; };
@ -30,7 +30,7 @@ type NotificationAssignment = {
notifyOnFailure: boolean; notifyOnFailure: boolean;
}; };
export const ScheduleNotificationsConfig = ({ scheduleId, destinations, initialData }: Props) => { export const ScheduleNotificationsConfig = ({ scheduleShortId, destinations, initialData }: Props) => {
const map = new Map<number, NotificationAssignment>(); const map = new Map<number, NotificationAssignment>();
for (const assignment of initialData) { for (const assignment of initialData) {
map.set(assignment.destinationId, { map.set(assignment.destinationId, {
@ -47,7 +47,7 @@ export const ScheduleNotificationsConfig = ({ scheduleId, destinations, initialD
const [isAddingNew, setIsAddingNew] = useState(false); const [isAddingNew, setIsAddingNew] = useState(false);
const { data: currentAssignments } = useQuery({ const { data: currentAssignments } = useQuery({
...getScheduleNotificationsOptions({ path: { scheduleId: scheduleId.toString() } }), ...getScheduleNotificationsOptions({ path: { shortId: scheduleShortId } }),
initialData, initialData,
}); });
@ -107,7 +107,7 @@ export const ScheduleNotificationsConfig = ({ scheduleId, destinations, initialD
const handleSave = () => { const handleSave = () => {
const assignmentsList = Array.from(assignments.values()); const assignmentsList = Array.from(assignments.values());
updateNotifications.mutate({ updateNotifications.mutate({
path: { scheduleId: scheduleId.toString() }, path: { shortId: scheduleShortId },
body: { body: {
assignments: assignmentsList, assignments: assignmentsList,
}, },

View file

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

View file

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

View file

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

View file

@ -27,8 +27,8 @@ export function BackupsPage() {
...listBackupSchedulesOptions(), ...listBackupSchedulesOptions(),
}); });
const [localItems, setLocalItems] = useState<number[] | null>(null); const [localItems, setLocalItems] = useState<string[] | null>(null);
const items = localItems ?? schedules?.map((s) => s.id) ?? []; const items = localItems ?? schedules?.map((s) => s.shortId) ?? [];
const sensors = useSensors( const sensors = useSensors(
useSensor(PointerSensor, { useSensor(PointerSensor, {
@ -48,14 +48,14 @@ export function BackupsPage() {
if (over && active.id !== over.id) { if (over && active.id !== over.id) {
setLocalItems((currentItems) => { setLocalItems((currentItems) => {
const baseItems = currentItems ?? schedules?.map((s) => s.id) ?? []; const baseItems = currentItems ?? schedules?.map((s) => s.shortId) ?? [];
const activeId = active.id as number; const activeId = String(active.id);
const overId = over.id as number; const overId = String(over.id);
let oldIndex = baseItems.indexOf(activeId); let oldIndex = baseItems.indexOf(activeId);
let newIndex = baseItems.indexOf(overId); let newIndex = baseItems.indexOf(overId);
if (oldIndex === -1 || newIndex === -1) { if (oldIndex === -1 || newIndex === -1) {
const freshItems = schedules?.map((s) => s.id) ?? []; const freshItems = schedules?.map((s) => s.shortId) ?? [];
oldIndex = freshItems.indexOf(activeId); oldIndex = freshItems.indexOf(activeId);
newIndex = freshItems.indexOf(overId); newIndex = freshItems.indexOf(overId);
@ -64,12 +64,12 @@ export function BackupsPage() {
} }
const newItems = arrayMove(freshItems, oldIndex, newIndex); const newItems = arrayMove(freshItems, oldIndex, newIndex);
reorderMutation.mutate({ body: { scheduleIds: newItems } }); reorderMutation.mutate({ body: { scheduleShortIds: newItems } });
return newItems; return newItems;
} }
const newItems = arrayMove(baseItems, oldIndex, newIndex); const newItems = arrayMove(baseItems, oldIndex, newIndex);
reorderMutation.mutate({ body: { scheduleIds: newItems } }); reorderMutation.mutate({ body: { scheduleShortIds: newItems } });
return 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 ( return (
<div className="container @container mx-auto space-y-6"> <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() { export function CreateBackupPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const formId = useId(); const formId = useId();
const [selectedVolumeId, setSelectedVolumeId] = useState<number | undefined>(); const [selectedVolumeShortId, setSelectedVolumeShortId] = useState<string | undefined>();
const { data: volumesData } = useSuspenseQuery({ const { data: volumesData } = useSuspenseQuery({
...listVolumesOptions(), ...listVolumesOptions(),
@ -33,7 +33,7 @@ export function CreateBackupPage() {
...createBackupScheduleMutation(), ...createBackupScheduleMutation(),
onSuccess: (data) => { onSuccess: (data) => {
toast.success("Backup job created successfully"); toast.success("Backup job created successfully");
void navigate({ to: `/backups/${data.id}` }); void navigate({ to: `/backups/${data.shortId}` });
}, },
onError: (error) => { onError: (error) => {
toast.error("Failed to create backup job", { toast.error("Failed to create backup job", {
@ -43,7 +43,7 @@ export function CreateBackupPage() {
}); });
const handleSubmit = (formValues: BackupScheduleFormValues) => { const handleSubmit = (formValues: BackupScheduleFormValues) => {
if (!selectedVolumeId) return; if (!selectedVolumeShortId) return;
const cronExpression = getCronExpression( const cronExpression = getCronExpression(
formValues.frequency, formValues.frequency,
@ -64,7 +64,7 @@ export function CreateBackupPage() {
createSchedule.mutate({ createSchedule.mutate({
body: { body: {
name: formValues.name, name: formValues.name,
volumeId: selectedVolumeId, volumeId: selectedVolumeShortId,
repositoryId: formValues.repositoryId, repositoryId: formValues.repositoryId,
enabled: true, enabled: true,
cronExpression, 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) { if (!volumesData.length) {
return ( return (
@ -113,13 +113,13 @@ export function CreateBackupPage() {
<div className="container mx-auto space-y-4"> <div className="container mx-auto space-y-4">
<Card> <Card>
<CardContent> <CardContent>
<Select value={selectedVolumeId?.toString()} onValueChange={(v) => setSelectedVolumeId(Number(v))}> <Select value={selectedVolumeShortId} onValueChange={setSelectedVolumeShortId}>
<SelectTrigger id="volume-select"> <SelectTrigger id="volume-select">
<SelectValue placeholder="Choose a volume to backup" /> <SelectValue placeholder="Choose a volume to backup" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{volumesData.map((volume) => ( {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"> <span className="flex items-center gap-2">
<HardDrive className="h-4 w-4" /> <HardDrive className="h-4 w-4" />
{volume.name} {volume.name}

View file

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

View file

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

View file

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

View file

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

View file

@ -22,7 +22,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => {
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const { data, isFetching, failureReason } = useQuery({ const { data, isFetching, failureReason } = useQuery({
...listSnapshotsOptions({ path: { id: repository.id } }), ...listSnapshotsOptions({ path: { shortId: repository.shortId } }),
initialData: [], initialData: [],
}); });
@ -41,7 +41,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => {
}); });
const handleRefresh = () => { const handleRefresh = () => {
refreshMutation.mutate({ path: { id: repository.id } }); refreshMutation.mutate({ path: { shortId: repository.shortId } });
}; };
const filteredSnapshots = data.filter((snapshot: Snapshot) => { const filteredSnapshots = data.filter((snapshot: Snapshot) => {
@ -173,7 +173,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => {
snapshots={filteredSnapshots} snapshots={filteredSnapshots}
repositoryId={repository.shortId} repositoryId={repository.shortId}
backups={schedules.data ?? []} 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"> <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 <OnOff
isOn={volume.autoRemount} isOn={volume.autoRemount}
toggle={() => toggle={() =>
toggleAutoRemount.mutate({ path: { id: volume.shortId }, body: { autoRemount: !volume.autoRemount } }) toggleAutoRemount.mutate({
path: { shortId: volume.shortId },
body: { autoRemount: !volume.autoRemount },
})
} }
disabled={toggleAutoRemount.isPending} disabled={toggleAutoRemount.isPending}
enabledLabel="Enabled" enabledLabel="Enabled"
@ -74,7 +77,7 @@ export const HealthchecksCard = ({ volume }: Props) => {
variant="outline" variant="outline"
className="mt-4" className="mt-4"
loading={healthcheck.isPending} 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" /> <Activity className="h-4 w-4 mr-2" />
Run Health Check Run Health Check

View file

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

View file

@ -103,7 +103,7 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
const confirmUpdate = () => { const confirmUpdate = () => {
if (pendingValues) { if (pendingValues) {
updateMutation.mutate({ updateMutation.mutate({
path: { id: volume.shortId }, path: { shortId: volume.shortId },
body: { name: pendingValues.name, config: pendingValues }, body: { name: pendingValues.name, config: pendingValues },
}); });
} }
@ -111,7 +111,7 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
const handleConfirmDelete = () => { const handleConfirmDelete = () => {
setShowDeleteConfirm(false); setShowDeleteConfirm(false);
deleteVol.mutate({ path: { id: volume.shortId } }); deleteVol.mutate({ path: { shortId: volume.shortId } });
}; };
return ( return (
@ -126,7 +126,7 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
{volume.status !== "mounted" ? ( {volume.status !== "mounted" ? (
<Button <Button
type="button" type="button"
onClick={() => mountVol.mutate({ path: { id: volume.shortId } })} onClick={() => mountVol.mutate({ path: { shortId: volume.shortId } })}
loading={mountVol.isPending} loading={mountVol.isPending}
> >
<Plug className="h-4 w-4 mr-2" /> <Plug className="h-4 w-4 mr-2" />
@ -136,7 +136,7 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
<Button <Button
type="button" type="button"
variant="secondary" variant="secondary"
onClick={() => unmountVol.mutate({ path: { id: volume.shortId } })} onClick={() => unmountVol.mutate({ path: { shortId: volume.shortId } })}
loading={unmountVol.isPending} loading={unmountVol.isPending}
> >
<Unplug className="h-4 w-4 mr-2" /> <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")({ export const Route = createFileRoute("/(dashboard)/backups/$backupId/$snapshotId/restore")({
component: RouteComponent, component: RouteComponent,
loader: async ({ params, context }) => { loader: async ({ params, context }) => {
const schedule = await getBackupSchedule({ path: { scheduleId: params.backupId } }); const schedule = await getBackupSchedule({ path: { shortId: params.backupId } });
if (!schedule.data) { if (!schedule.data) {
throw new Response("Not Found", { status: 404 }); 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([ const [snapshot, repository] = await Promise.all([
context.queryClient.ensureQueryData({ 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 { return {

View file

@ -17,16 +17,19 @@ export const Route = createFileRoute("/(dashboard)/backups/$backupId/")({
const { backupId } = params; const { backupId } = params;
const [schedule, notifs, repos, scheduleNotifs, mirrors] = await Promise.all([ 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({ ...listNotificationDestinationsOptions() }),
context.queryClient.ensureQueryData({ ...listRepositoriesOptions() }), context.queryClient.ensureQueryData({ ...listRepositoriesOptions() }),
context.queryClient.ensureQueryData({ ...getScheduleNotificationsOptions({ path: { scheduleId: backupId } }) }), context.queryClient.ensureQueryData({ ...getScheduleNotificationsOptions({ path: { shortId: backupId } }) }),
context.queryClient.ensureQueryData({ ...getScheduleMirrorsOptions({ path: { scheduleId: backupId } }) }), context.queryClient.ensureQueryData({ ...getScheduleMirrorsOptions({ path: { shortId: backupId } }) }),
]) ]);
void context.queryClient.prefetchQuery({ 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 }; 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>, errorComponent: (e) => <div>{e.error.message}</div>,
loader: async ({ params, context }) => { loader: async ({ params, context }) => {
const res = await context.queryClient.ensureQueryData({ const res = await context.queryClient.ensureQueryData({
...getRepositoryOptions({ path: { id: params.repositoryId } }), ...getRepositoryOptions({ path: { shortId: params.repositoryId } }),
}); });
return res; return res;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -42,7 +42,7 @@ const backupScheduleSchema = type({
); );
const scheduleMirrorSchema = type({ const scheduleMirrorSchema = type({
scheduleId: "number", scheduleId: "string",
repositoryId: "string", repositoryId: "string",
enabled: "boolean", enabled: "boolean",
lastCopyAt: "number | null", lastCopyAt: "number | null",
@ -125,7 +125,7 @@ export const getBackupScheduleForVolumeDto = describeRoute({
*/ */
export const createBackupScheduleBody = type({ export const createBackupScheduleBody = type({
name: "1 <= string <= 128", name: "1 <= string <= 128",
volumeId: "number", volumeId: "string | number",
repositoryId: "string", repositoryId: "string",
enabled: "boolean", enabled: "boolean",
cronExpression: "string", cronExpression: "string",
@ -376,7 +376,7 @@ export const getMirrorCompatibilityDto = describeRoute({
* Reorder backup schedules * Reorder backup schedules
*/ */
export const reorderBackupSchedulesBody = type({ export const reorderBackupSchedulesBody = type({
scheduleIds: "number[]", scheduleShortIds: "string[]",
}); });
export type ReorderBackupSchedulesBody = typeof reorderBackupSchedulesBody.infer; export type ReorderBackupSchedulesBody = typeof reorderBackupSchedulesBody.infer;
@ -388,7 +388,7 @@ export const reorderBackupSchedulesResponse = type({
export type ReorderBackupSchedulesDto = typeof reorderBackupSchedulesResponse.infer; export type ReorderBackupSchedulesDto = typeof reorderBackupSchedulesResponse.infer;
export const reorderBackupSchedulesDto = describeRoute({ 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", operationId: "reorderBackupSchedules",
tags: ["Backups"], tags: ["Backups"],
responses: { responses: {

View file

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

View file

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

View file

@ -186,7 +186,7 @@ describe("repositories updates", () => {
const { token, organizationId } = await createTestSession(); const { token, organizationId } = await createTestSession();
const repository = await createRepositoryRecord(organizationId); 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", method: "PATCH",
headers: { headers: {
...getAuthHeaders(token), ...getAuthHeaders(token),
@ -212,7 +212,7 @@ describe("repositories updates", () => {
const { token, organizationId } = await createTestSession(); const { token, organizationId } = await createTestSession();
const repository = await createRepositoryRecord(organizationId); 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", method: "PATCH",
headers: { headers: {
...getAuthHeaders(token), ...getAuthHeaders(token),

View file

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

View file

@ -79,25 +79,25 @@ export const repositoriesController = new Hono()
return c.json(remotes); return c.json(remotes);
}) })
.get("/:id", getRepositoryDto, async (c) => { .get("/:shortId", getRepositoryDto, async (c) => {
const { id } = c.req.param(); const { shortId } = c.req.param();
const res = await repositoriesService.getRepository(id); const res = await repositoriesService.getRepository(shortId);
return c.json<GetRepositoryDto>(res.repository, 200); return c.json<GetRepositoryDto>(res.repository, 200);
}) })
.delete("/:id", deleteRepositoryDto, async (c) => { .delete("/:shortId", deleteRepositoryDto, async (c) => {
const { id } = c.req.param(); const { shortId } = c.req.param();
await repositoriesService.deleteRepository(id); await repositoriesService.deleteRepository(shortId);
return c.json<DeleteRepositoryDto>({ message: "Repository deleted" }, 200); return c.json<DeleteRepositoryDto>({ message: "Repository deleted" }, 200);
}) })
.get("/:id/snapshots", listSnapshotsDto, validator("query", listSnapshotsFilters), async (c) => { .get("/:shortId/snapshots", listSnapshotsDto, validator("query", listSnapshotsFilters), async (c) => {
const { id } = c.req.param(); const { shortId } = c.req.param();
const { backupId } = c.req.valid("query"); const { backupId } = c.req.valid("query");
const [res, retentionCategories] = await Promise.all([ const [res, retentionCategories] = await Promise.all([
repositoriesService.listSnapshots(id, backupId), repositoriesService.listSnapshots(shortId, backupId),
repositoriesService.getRetentionCategories(id, backupId), repositoriesService.getRetentionCategories(shortId, backupId),
]); ]);
const snapshots = res.map((snapshot) => { const snapshots = res.map((snapshot) => {
@ -119,15 +119,15 @@ export const repositoriesController = new Hono()
return c.json<ListSnapshotsDto>(snapshots, 200); return c.json<ListSnapshotsDto>(snapshots, 200);
}) })
.post("/:id/snapshots/refresh", refreshSnapshotsDto, async (c) => { .post("/:shortId/snapshots/refresh", refreshSnapshotsDto, async (c) => {
const { id } = c.req.param(); const { shortId } = c.req.param();
const result = await repositoriesService.refreshSnapshots(id); const result = await repositoriesService.refreshSnapshots(shortId);
return c.json<RefreshSnapshotsDto>(result, 200); return c.json<RefreshSnapshotsDto>(result, 200);
}) })
.get("/:id/snapshots/:snapshotId", getSnapshotDetailsDto, async (c) => { .get("/:shortId/snapshots/:snapshotId", getSnapshotDetailsDto, async (c) => {
const { id, snapshotId } = c.req.param(); const { shortId, snapshotId } = c.req.param();
const snapshot = await repositoriesService.getSnapshotDetails(id, snapshotId); const snapshot = await repositoriesService.getSnapshotDetails(shortId, snapshotId);
const duration = getSnapshotDuration(snapshot.summary); const duration = getSnapshotDuration(snapshot.summary);
@ -146,11 +146,11 @@ export const repositoriesController = new Hono()
return c.json<GetSnapshotDetailsDto>(response, 200); return c.json<GetSnapshotDetailsDto>(response, 200);
}) })
.get( .get(
"/:id/snapshots/:snapshotId/files", "/:shortId/snapshots/:snapshotId/files",
listSnapshotFilesDto, listSnapshotFilesDto,
validator("query", listSnapshotFilesQuery), validator("query", listSnapshotFilesQuery),
async (c) => { async (c) => {
const { id, snapshotId } = c.req.param(); const { shortId, snapshotId } = c.req.param();
const { path, ...query } = c.req.valid("query"); const { path, ...query } = c.req.valid("query");
const decodedPath = path ? decodeURIComponent(path) : undefined; 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 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 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"); c.header("Cache-Control", "max-age=300, stale-while-revalidate=600");
return c.json<ListSnapshotFilesDto>(result, 200); return c.json<ListSnapshotFilesDto>(result, 200);
}, },
) )
.post("/:id/restore", restoreSnapshotDto, validator("json", restoreSnapshotBody), async (c) => { .post("/:shortId/restore", restoreSnapshotDto, validator("json", restoreSnapshotBody), async (c) => {
const { id } = c.req.param(); const { shortId } = c.req.param();
const { snapshotId, ...options } = c.req.valid("json"); 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); return c.json<RestoreSnapshotDto>(result, 200);
}) })
.post("/:id/doctor", startDoctorDto, async (c) => { .post("/:shortId/doctor", startDoctorDto, async (c) => {
const { id } = c.req.param(); const { shortId } = c.req.param();
const result = await repositoriesService.startDoctor(id); const result = await repositoriesService.startDoctor(shortId);
return c.json<StartDoctorDto>(result, 202); return c.json<StartDoctorDto>(result, 202);
}) })
.delete("/:id/doctor", cancelDoctorDto, async (c) => { .delete("/:shortId/doctor", cancelDoctorDto, async (c) => {
const { id } = c.req.param(); const { shortId } = c.req.param();
const result = await repositoriesService.cancelDoctor(id); const result = await repositoriesService.cancelDoctor(shortId);
return c.json<CancelDoctorDto>(result, 200); return c.json<CancelDoctorDto>(result, 200);
}) })
.post("/:id/unlock", unlockRepositoryDto, async (c) => { .post("/:shortId/unlock", unlockRepositoryDto, async (c) => {
const { id } = c.req.param(); const { shortId } = c.req.param();
const result = await repositoriesService.unlockRepository(id); const result = await repositoriesService.unlockRepository(shortId);
return c.json<UnlockRepositoryDto>(result, 200); return c.json<UnlockRepositoryDto>(result, 200);
}) })
.delete("/:id/snapshots/:snapshotId", deleteSnapshotDto, async (c) => { .delete("/:shortId/snapshots/:snapshotId", deleteSnapshotDto, async (c) => {
const { id, snapshotId } = c.req.param(); const { shortId, snapshotId } = c.req.param();
await repositoriesService.deleteSnapshot(id, snapshotId); await repositoriesService.deleteSnapshot(shortId, snapshotId);
return c.json<DeleteSnapshotDto>({ message: "Snapshot deleted" }, 200); return c.json<DeleteSnapshotDto>({ message: "Snapshot deleted" }, 200);
}) })
.delete("/:id/snapshots", deleteSnapshotsDto, validator("json", deleteSnapshotsBody), async (c) => { .delete("/:shortId/snapshots", deleteSnapshotsDto, validator("json", deleteSnapshotsBody), async (c) => {
const { id } = c.req.param(); const { shortId } = c.req.param();
const { snapshotIds } = c.req.valid("json"); const { snapshotIds } = c.req.valid("json");
await repositoriesService.deleteSnapshots(id, snapshotIds); await repositoriesService.deleteSnapshots(shortId, snapshotIds);
return c.json<DeleteSnapshotsResponseDto>({ message: "Snapshots deleted" }, 200); return c.json<DeleteSnapshotsResponseDto>({ message: "Snapshots deleted" }, 200);
}) })
.post("/:id/snapshots/tag", tagSnapshotsDto, validator("json", tagSnapshotsBody), async (c) => { .post("/:shortId/snapshots/tag", tagSnapshotsDto, validator("json", tagSnapshotsBody), async (c) => {
const { id } = c.req.param(); const { shortId } = c.req.param();
const { snapshotIds, ...tags } = c.req.valid("json"); 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); return c.json<TagSnapshotsResponseDto>({ message: "Snapshots tagged" }, 200);
}) })
.patch("/:id", updateRepositoryDto, validator("json", updateRepositoryBody), async (c) => { .patch("/:shortId", updateRepositoryDto, validator("json", updateRepositoryBody), async (c) => {
const { id } = c.req.param(); const { shortId } = c.req.param();
const body = c.req.valid("json"); 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); return c.json<UpdateRepositoryDto>(res.repository, 200);
}) })
.post( .post(
"/:id/exec", "/:shortId/exec",
requireDevPanel, requireDevPanel,
requireOrgAdmin, requireOrgAdmin,
devPanelExecDto, devPanelExecDto,
validator("json", devPanelExecBody), validator("json", devPanelExecBody),
async (c) => { async (c) => {
const { id } = c.req.param(); const { shortId } = c.req.param();
const body = c.req.valid("json"); const body = c.req.valid("json");
return streamSSE(c, async (stream) => { return streamSSE(c, async (stream) => {
@ -240,7 +240,7 @@ export const repositoriesController = new Hono()
try { try {
const result = await repositoriesService.execResticCommand( const result = await repositoriesService.execResticCommand(
id, shortId,
body.command, body.command,
body.args, body.args,
async (line) => sendSSE("output", { type: "stdout", line }), 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 runningDoctors = new Map<string, AbortController>();
const findRepository = async (idOrShortId: string) => { const findRepository = async (shortId: string) => {
const organizationId = getOrganizationId(); const organizationId = getOrganizationId();
return await db.query.repositoriesTable.findFirst({ return await db.query.repositoriesTable.findFirst({
where: { 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}`); throw new InternalServerError(`Failed to initialize repository: ${errorMessage}`);
}; };
const getRepository = async (id: string) => { const getRepository = async (shortId: string) => {
const repository = await findRepository(id); const repository = await findRepository(shortId);
if (!repository) { if (!repository) {
throw new NotFoundError("Repository not found"); throw new NotFoundError("Repository not found");
@ -192,8 +192,8 @@ const getRepository = async (id: string) => {
return { repository }; return { repository };
}; };
const deleteRepository = async (id: string) => { const deleteRepository = async (shortId: string) => {
const repository = await findRepository(id); const repository = await findRepository(shortId);
if (!repository) { if (!repository) {
throw new NotFoundError("Repository not found"); throw new NotFoundError("Repository not found");
@ -214,14 +214,14 @@ const deleteRepository = async (id: string) => {
/** /**
* List snapshots for a given repository * List snapshots for a given repository
* If backupId is provided, filter snapshots by that backup ID (tag) * 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 * @param backupId Optional backup ID to filter snapshots for a specific backup schedule
* *
* @returns List of snapshots * @returns List of snapshots
*/ */
const listSnapshots = async (id: string, backupId?: string) => { const listSnapshots = async (shortId: string, backupId?: string) => {
const organizationId = getOrganizationId(); const organizationId = getOrganizationId();
const repository = await findRepository(id); const repository = await findRepository(shortId);
if (!repository) { if (!repository) {
throw new NotFoundError("Repository not found"); throw new NotFoundError("Repository not found");
@ -252,13 +252,13 @@ const listSnapshots = async (id: string, backupId?: string) => {
}; };
const listSnapshotFiles = async ( const listSnapshotFiles = async (
id: string, shortId: string,
snapshotId: string, snapshotId: string,
path?: string, path?: string,
options?: { offset?: number; limit?: number }, options?: { offset?: number; limit?: number },
) => { ) => {
const organizationId = getOrganizationId(); const organizationId = getOrganizationId();
const repository = await findRepository(id); const repository = await findRepository(shortId);
if (!repository) { if (!repository) {
throw new NotFoundError("Repository not found"); throw new NotFoundError("Repository not found");
@ -317,7 +317,7 @@ const listSnapshotFiles = async (
}; };
const restoreSnapshot = async ( const restoreSnapshot = async (
id: string, shortId: string,
snapshotId: string, snapshotId: string,
options?: { options?: {
include?: string[]; include?: string[];
@ -329,7 +329,7 @@ const restoreSnapshot = async (
}, },
) => { ) => {
const organizationId = getOrganizationId(); const organizationId = getOrganizationId();
const repository = await findRepository(id); const repository = await findRepository(shortId);
if (!repository) { if (!repository) {
throw new NotFoundError("Repository not found"); throw new NotFoundError("Repository not found");
@ -337,14 +337,14 @@ const restoreSnapshot = async (
const target = options?.targetPath || "/"; const target = options?.targetPath || "/";
const { paths } = await getSnapshotDetails(repository.id, snapshotId); const { paths } = await getSnapshotDetails(repository.shortId, snapshotId);
const basePath = findCommonAncestor(paths); const basePath = findCommonAncestor(paths);
const releaseLock = await repoMutex.acquireShared(repository.id, `restore:${snapshotId}`); const releaseLock = await repoMutex.acquireShared(repository.id, `restore:${snapshotId}`);
try { try {
serverEvents.emit("restore:started", { serverEvents.emit("restore:started", {
organizationId, organizationId,
repositoryId: repository.id, repositoryId: repository.shortId,
snapshotId, snapshotId,
}); });
@ -355,7 +355,7 @@ const restoreSnapshot = async (
onProgress: (progress) => { onProgress: (progress) => {
serverEvents.emit("restore:progress", { serverEvents.emit("restore:progress", {
organizationId, organizationId,
repositoryId: repository.id, repositoryId: repository.shortId,
snapshotId, snapshotId,
...progress, ...progress,
}); });
@ -364,7 +364,7 @@ const restoreSnapshot = async (
serverEvents.emit("restore:completed", { serverEvents.emit("restore:completed", {
organizationId, organizationId,
repositoryId: repository.id, repositoryId: repository.shortId,
snapshotId, snapshotId,
status: "success", status: "success",
}); });
@ -378,7 +378,7 @@ const restoreSnapshot = async (
} catch (error) { } catch (error) {
serverEvents.emit("restore:completed", { serverEvents.emit("restore:completed", {
organizationId, organizationId,
repositoryId: repository.id, repositoryId: repository.shortId,
snapshotId, snapshotId,
status: "error", status: "error",
error: toMessage(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 organizationId = getOrganizationId();
const repository = await findRepository(id); const repository = await findRepository(shortId);
if (!repository) { if (!repository) {
throw new NotFoundError("Repository not found"); 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); const snapshot = snapshots.find((snap) => snap.id === snapshotId || snap.short_id === snapshotId);
if (!snapshot) { if (!snapshot) {
void refreshSnapshots(id).catch(() => {}); void refreshSnapshots(shortId).catch(() => {});
throw new NotFoundError("Snapshot not found"); throw new NotFoundError("Snapshot not found");
} }
@ -450,8 +450,8 @@ const checkHealth = async (repositoryId: string) => {
} }
}; };
const startDoctor = async (id: string) => { const startDoctor = async (shortId: string) => {
const repository = await findRepository(id); const repository = await findRepository(shortId);
if (!repository) { if (!repository) {
throw new NotFoundError("Repository not found"); throw new NotFoundError("Repository not found");
@ -468,7 +468,7 @@ const startDoctor = async (id: string) => {
serverEvents.emit("doctor:started", { serverEvents.emit("doctor:started", {
organizationId: repository.organizationId, organizationId: repository.organizationId,
repositoryId: repository.id, repositoryId: repository.shortId,
repositoryName: repository.name, repositoryName: repository.name,
}); });
@ -478,7 +478,7 @@ const startDoctor = async (id: string) => {
throw error; throw error;
} }
executeDoctor(repository.id, repository.config, repository.name, abortController.signal) executeDoctor(repository.id, repository.shortId, repository.config, repository.name, abortController.signal)
.catch((error) => { .catch((error) => {
logger.error(`Doctor background task failed: ${toMessage(error)}`); logger.error(`Doctor background task failed: ${toMessage(error)}`);
}) })
@ -486,11 +486,11 @@ const startDoctor = async (id: string) => {
runningDoctors.delete(repository.id); 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 cancelDoctor = async (shortId: string) => {
const repository = await findRepository(id); const repository = await findRepository(shortId);
if (!repository) { if (!repository) {
throw new NotFoundError("Repository not found"); throw new NotFoundError("Repository not found");
@ -509,16 +509,16 @@ const cancelDoctor = async (id: string) => {
serverEvents.emit("doctor:cancelled", { serverEvents.emit("doctor:cancelled", {
organizationId: repository.organizationId, organizationId: repository.organizationId,
repositoryId: repository.id, repositoryId: repository.shortId,
repositoryName: repository.name, repositoryName: repository.name,
}); });
return { message: "Doctor operation cancelled" }; return { message: "Doctor operation cancelled" };
}; };
const deleteSnapshot = async (id: string, snapshotId: string) => { const deleteSnapshot = async (shortId: string, snapshotId: string) => {
const organizationId = getOrganizationId(); const organizationId = getOrganizationId();
const repository = await findRepository(id); const repository = await findRepository(shortId);
if (!repository) { if (!repository) {
throw new NotFoundError("Repository not found"); 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 organizationId = getOrganizationId();
const repository = await findRepository(id); const repository = await findRepository(shortId);
if (!repository) { if (!repository) {
throw new NotFoundError("Repository not found"); throw new NotFoundError("Repository not found");
@ -555,12 +555,12 @@ const deleteSnapshots = async (id: string, snapshotIds: string[]) => {
}; };
const tagSnapshots = async ( const tagSnapshots = async (
id: string, shortId: string,
snapshotIds: string[], snapshotIds: string[],
tags: { add?: string[]; remove?: string[]; set?: string[] }, tags: { add?: string[]; remove?: string[]; set?: string[] },
) => { ) => {
const organizationId = getOrganizationId(); const organizationId = getOrganizationId();
const repository = await findRepository(id); const repository = await findRepository(shortId);
if (!repository) { if (!repository) {
throw new NotFoundError("Repository not found"); 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 organizationId = getOrganizationId();
const repository = await findRepository(id); const repository = await findRepository(shortId);
if (!repository) { if (!repository) {
throw new NotFoundError("Repository not found"); throw new NotFoundError("Repository not found");
@ -604,8 +604,8 @@ const refreshSnapshots = async (id: string) => {
} }
}; };
const updateRepository = async (id: string, updates: UpdateRepositoryBody) => { const updateRepository = async (shortId: string, updates: UpdateRepositoryBody) => {
const existing = await findRepository(id); const existing = await findRepository(shortId);
if (!existing) { if (!existing) {
throw new NotFoundError("Repository not found"); throw new NotFoundError("Repository not found");
@ -676,9 +676,9 @@ const updateRepository = async (id: string, updates: UpdateRepositoryBody) => {
return { repository: updated }; return { repository: updated };
}; };
const unlockRepository = async (id: string) => { const unlockRepository = async (shortId: string) => {
const organizationId = getOrganizationId(); const organizationId = getOrganizationId();
const repository = await findRepository(id); const repository = await findRepository(shortId);
if (!repository) { if (!repository) {
throw new NotFoundError("Repository not found"); throw new NotFoundError("Repository not found");
@ -694,7 +694,7 @@ const unlockRepository = async (id: string) => {
}; };
const execResticCommand = async ( const execResticCommand = async (
id: string, shortId: string,
command: string, command: string,
args: string[] | undefined, args: string[] | undefined,
onStdout: (line: string) => void, onStdout: (line: string) => void,
@ -702,7 +702,7 @@ const execResticCommand = async (
signal?: AbortSignal, signal?: AbortSignal,
) => { ) => {
const organizationId = getOrganizationId(); const organizationId = getOrganizationId();
const repository = await findRepository(id); const repository = await findRepository(shortId);
if (!repository) { if (!repository) {
throw new NotFoundError("Repository not found"); throw new NotFoundError("Repository not found");

View file

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

View file

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