From 5f53fd9f190de77feeae92cc9fdb933782fb1345 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Tue, 30 Jun 2026 16:07:34 +0200 Subject: [PATCH] feat: expose snapshot restore planning API The restore UI needs a target-scoped answer for the snapshot query root and whether original-location restore is available. That decision now belongs to core planning and must account for the selected restore agent platform --- .../api-client/@tanstack/react-query.gen.ts | 22 ++- app/client/api-client/client/index.ts | 2 + app/client/api-client/client/utils.gen.ts | 4 +- app/client/api-client/core/auth.gen.ts | 7 + app/client/api-client/core/params.gen.ts | 18 +- .../api-client/core/pathSerializer.gen.ts | 12 +- .../api-client/core/queryKeySerializer.gen.ts | 2 +- app/client/api-client/core/types.gen.ts | 6 + app/client/api-client/core/utils.gen.ts | 8 +- app/client/api-client/index.ts | 4 + app/client/api-client/sdk.gen.ts | 175 +++++++++--------- app/client/api-client/types.gen.ts | 24 +++ .../__tests__/repositories.controller.test.ts | 20 ++ .../repositories/repositories.controller.ts | 16 ++ .../modules/repositories/repositories.dto.ts | 38 ++++ 15 files changed, 250 insertions(+), 108 deletions(-) diff --git a/app/client/api-client/@tanstack/react-query.gen.ts b/app/client/api-client/@tanstack/react-query.gen.ts index d5f66cff3..8fa24b277 100644 --- a/app/client/api-client/@tanstack/react-query.gen.ts +++ b/app/client/api-client/@tanstack/react-query.gen.ts @@ -4,8 +4,8 @@ import { type DefaultError, type InfiniteData, infiniteQueryOptions, queryOptions, type UseMutationOptions } from '@tanstack/react-query'; import { client } from '../client.gen'; -import { browseFilesystem, cancelDoctor, createApiKey, createBackupSchedule, createDesktopSession, createNotificationDestination, createRepository, createVolume, deleteApiKey, deleteBackupSchedule, deleteNotificationDestination, deleteRepository, deleteSnapshot, deleteSnapshots, deleteSsoInvitation, deleteSsoProvider, deleteUserAccount, deleteVolume, downloadResticPassword, dumpSnapshot, getAdminUsers, getApiKeys, getBackupProgress, getBackupSchedule, getBackupScheduleForVolume, getDevPanel, getMirrorCompatibility, getMirrorSyncStatus, getNotificationDestination, getOrgMembers, getPasswordLoginStatus, getPublicSsoProviders, getRegistrationStatus, getRepository, getRepositoryStats, getScheduleMirrors, getScheduleNotifications, getSnapshotDetails, getSsoSettings, getStatus, getSystemInfo, getUpdates, getUserDeletionImpact, getUserSsoInvitations, getVolume, healthCheckVolume, listBackupSchedules, listFiles, listNotificationDestinations, listRcloneRemotes, listRepositories, listSnapshotFiles, listSnapshots, listVolumes, mountVolume, type Options, refreshRepositoryStats, refreshSnapshots, removeOrgMember, reorderBackupSchedules, restoreSnapshot, runBackupNow, runForget, setPasswordLoginStatus, setRegistrationStatus, startDoctor, startInvitationSsoVerification, stopBackup, syncMirror, tagSnapshots, testConnection, testNotificationDestination, unlockRepository, unmountVolume, updateBackupSchedule, updateMemberRole, updateNotificationDestination, updateRepository, updateScheduleMirrors, updateScheduleNotifications, updateSsoProviderAutoLinking, updateVolume } from '../sdk.gen'; -import type { BrowseFilesystemData, BrowseFilesystemResponse, CancelDoctorData, CancelDoctorResponse, CreateApiKeyData, CreateApiKeyResponse, CreateBackupScheduleData, CreateBackupScheduleResponse, CreateDesktopSessionData, CreateNotificationDestinationData, CreateNotificationDestinationResponse, CreateRepositoryData, CreateRepositoryResponse, CreateVolumeData, CreateVolumeResponse, DeleteApiKeyData, DeleteBackupScheduleData, DeleteBackupScheduleResponse, DeleteNotificationDestinationData, DeleteNotificationDestinationResponse, DeleteRepositoryData, DeleteRepositoryResponse, DeleteSnapshotData, DeleteSnapshotResponse, DeleteSnapshotsData, DeleteSnapshotsResponse, DeleteSsoInvitationData, DeleteSsoProviderData, DeleteUserAccountData, DeleteVolumeData, DeleteVolumeResponse, DownloadResticPasswordData, DownloadResticPasswordResponse, DumpSnapshotData, DumpSnapshotResponse, GetAdminUsersData, GetAdminUsersResponse, GetApiKeysData, GetApiKeysResponse, GetBackupProgressData, GetBackupProgressResponse, GetBackupScheduleData, GetBackupScheduleForVolumeData, GetBackupScheduleForVolumeResponse, GetBackupScheduleResponse, GetDevPanelData, GetDevPanelResponse, GetMirrorCompatibilityData, GetMirrorCompatibilityResponse, GetMirrorSyncStatusData, GetMirrorSyncStatusResponse, GetNotificationDestinationData, GetNotificationDestinationResponse, GetOrgMembersData, GetOrgMembersResponse, GetPasswordLoginStatusData, GetPasswordLoginStatusResponse, GetPublicSsoProvidersData, GetPublicSsoProvidersResponse, GetRegistrationStatusData, GetRegistrationStatusResponse, GetRepositoryData, GetRepositoryResponse, GetRepositoryStatsData, GetRepositoryStatsResponse, GetScheduleMirrorsData, GetScheduleMirrorsResponse, GetScheduleNotificationsData, GetScheduleNotificationsResponse, GetSnapshotDetailsData, GetSnapshotDetailsResponse, GetSsoSettingsData, GetSsoSettingsResponse, GetStatusData, GetStatusResponse, GetSystemInfoData, GetSystemInfoResponse, GetUpdatesData, GetUpdatesResponse, GetUserDeletionImpactData, GetUserDeletionImpactResponse, GetUserSsoInvitationsData, GetUserSsoInvitationsResponse, GetVolumeData, GetVolumeResponse, HealthCheckVolumeData, HealthCheckVolumeResponse, ListBackupSchedulesData, ListBackupSchedulesResponse, ListFilesData, ListFilesResponse, ListNotificationDestinationsData, ListNotificationDestinationsResponse, ListRcloneRemotesData, ListRcloneRemotesResponse, ListRepositoriesData, ListRepositoriesResponse, ListSnapshotFilesData, ListSnapshotFilesResponse, ListSnapshotsData, ListSnapshotsResponse, ListVolumesData, ListVolumesResponse, MountVolumeData, MountVolumeResponse, RefreshRepositoryStatsData, RefreshRepositoryStatsResponse, RefreshSnapshotsData, RefreshSnapshotsResponse, RemoveOrgMemberData, ReorderBackupSchedulesData, ReorderBackupSchedulesResponse, RestoreSnapshotData, RestoreSnapshotResponse, RunBackupNowData, RunBackupNowResponse, RunForgetData, RunForgetResponse, SetPasswordLoginStatusData, SetPasswordLoginStatusResponse, SetRegistrationStatusData, SetRegistrationStatusResponse, StartDoctorData, StartDoctorResponse, StartInvitationSsoVerificationData, StopBackupData, StopBackupResponse, SyncMirrorData, SyncMirrorResponse, TagSnapshotsData, TagSnapshotsResponse, TestConnectionData, TestConnectionResponse, TestNotificationDestinationData, TestNotificationDestinationResponse, UnlockRepositoryData, UnlockRepositoryResponse, UnmountVolumeData, UnmountVolumeResponse, UpdateBackupScheduleData, UpdateBackupScheduleResponse, UpdateMemberRoleData, UpdateNotificationDestinationData, UpdateNotificationDestinationResponse, UpdateRepositoryData, UpdateRepositoryResponse, UpdateScheduleMirrorsData, UpdateScheduleMirrorsResponse, UpdateScheduleNotificationsData, UpdateScheduleNotificationsResponse, UpdateSsoProviderAutoLinkingData, UpdateVolumeData, UpdateVolumeResponse } from '../types.gen'; +import { browseFilesystem, cancelDoctor, createApiKey, createBackupSchedule, createDesktopSession, createNotificationDestination, createRepository, createVolume, deleteApiKey, deleteBackupSchedule, deleteNotificationDestination, deleteRepository, deleteSnapshot, deleteSnapshots, deleteSsoInvitation, deleteSsoProvider, deleteUserAccount, deleteVolume, downloadResticPassword, dumpSnapshot, getAdminUsers, getApiKeys, getBackupProgress, getBackupSchedule, getBackupScheduleForVolume, getDevPanel, getMirrorCompatibility, getMirrorSyncStatus, getNotificationDestination, getOrgMembers, getPasswordLoginStatus, getPublicSsoProviders, getRegistrationStatus, getRepository, getRepositoryStats, getScheduleMirrors, getScheduleNotifications, getSnapshotDetails, getSnapshotRestorePlan, getSsoSettings, getStatus, getSystemInfo, getUpdates, getUserDeletionImpact, getUserSsoInvitations, getVolume, healthCheckVolume, listBackupSchedules, listFiles, listNotificationDestinations, listRcloneRemotes, listRepositories, listSnapshotFiles, listSnapshots, listVolumes, mountVolume, type Options, refreshRepositoryStats, refreshSnapshots, removeOrgMember, reorderBackupSchedules, restoreSnapshot, runBackupNow, runForget, setPasswordLoginStatus, setRegistrationStatus, startDoctor, startInvitationSsoVerification, stopBackup, syncMirror, tagSnapshots, testConnection, testNotificationDestination, unlockRepository, unmountVolume, updateBackupSchedule, updateMemberRole, updateNotificationDestination, updateRepository, updateScheduleMirrors, updateScheduleNotifications, updateSsoProviderAutoLinking, updateVolume } from '../sdk.gen'; +import type { BrowseFilesystemData, BrowseFilesystemResponse, CancelDoctorData, CancelDoctorResponse, CreateApiKeyData, CreateApiKeyResponse, CreateBackupScheduleData, CreateBackupScheduleResponse, CreateDesktopSessionData, CreateNotificationDestinationData, CreateNotificationDestinationResponse, CreateRepositoryData, CreateRepositoryResponse, CreateVolumeData, CreateVolumeResponse, DeleteApiKeyData, DeleteBackupScheduleData, DeleteBackupScheduleResponse, DeleteNotificationDestinationData, DeleteNotificationDestinationResponse, DeleteRepositoryData, DeleteRepositoryResponse, DeleteSnapshotData, DeleteSnapshotResponse, DeleteSnapshotsData, DeleteSnapshotsResponse, DeleteSsoInvitationData, DeleteSsoProviderData, DeleteUserAccountData, DeleteVolumeData, DeleteVolumeResponse, DownloadResticPasswordData, DownloadResticPasswordResponse, DumpSnapshotData, DumpSnapshotResponse, GetAdminUsersData, GetAdminUsersResponse, GetApiKeysData, GetApiKeysResponse, GetBackupProgressData, GetBackupProgressResponse, GetBackupScheduleData, GetBackupScheduleForVolumeData, GetBackupScheduleForVolumeResponse, GetBackupScheduleResponse, GetDevPanelData, GetDevPanelResponse, GetMirrorCompatibilityData, GetMirrorCompatibilityResponse, GetMirrorSyncStatusData, GetMirrorSyncStatusResponse, GetNotificationDestinationData, GetNotificationDestinationResponse, GetOrgMembersData, GetOrgMembersResponse, GetPasswordLoginStatusData, GetPasswordLoginStatusResponse, GetPublicSsoProvidersData, GetPublicSsoProvidersResponse, GetRegistrationStatusData, GetRegistrationStatusResponse, GetRepositoryData, GetRepositoryResponse, GetRepositoryStatsData, GetRepositoryStatsResponse, GetScheduleMirrorsData, GetScheduleMirrorsResponse, GetScheduleNotificationsData, GetScheduleNotificationsResponse, GetSnapshotDetailsData, GetSnapshotDetailsResponse, GetSnapshotRestorePlanData, GetSnapshotRestorePlanResponse, GetSsoSettingsData, GetSsoSettingsResponse, GetStatusData, GetStatusResponse, GetSystemInfoData, GetSystemInfoResponse, GetUpdatesData, GetUpdatesResponse, GetUserDeletionImpactData, GetUserDeletionImpactResponse, GetUserSsoInvitationsData, GetUserSsoInvitationsResponse, GetVolumeData, GetVolumeResponse, HealthCheckVolumeData, HealthCheckVolumeResponse, ListBackupSchedulesData, ListBackupSchedulesResponse, ListFilesData, ListFilesResponse, ListNotificationDestinationsData, ListNotificationDestinationsResponse, ListRcloneRemotesData, ListRcloneRemotesResponse, ListRepositoriesData, ListRepositoriesResponse, ListSnapshotFilesData, ListSnapshotFilesResponse, ListSnapshotsData, ListSnapshotsResponse, ListVolumesData, ListVolumesResponse, MountVolumeData, MountVolumeResponse, RefreshRepositoryStatsData, RefreshRepositoryStatsResponse, RefreshSnapshotsData, RefreshSnapshotsResponse, RemoveOrgMemberData, ReorderBackupSchedulesData, ReorderBackupSchedulesResponse, RestoreSnapshotData, RestoreSnapshotResponse, RunBackupNowData, RunBackupNowResponse, RunForgetData, RunForgetResponse, SetPasswordLoginStatusData, SetPasswordLoginStatusResponse, SetRegistrationStatusData, SetRegistrationStatusResponse, StartDoctorData, StartDoctorResponse, StartInvitationSsoVerificationData, StopBackupData, StopBackupResponse, SyncMirrorData, SyncMirrorResponse, TagSnapshotsData, TagSnapshotsResponse, TestConnectionData, TestConnectionResponse, TestNotificationDestinationData, TestNotificationDestinationResponse, UnlockRepositoryData, UnlockRepositoryResponse, UnmountVolumeData, UnmountVolumeResponse, UpdateBackupScheduleData, UpdateBackupScheduleResponse, UpdateMemberRoleData, UpdateNotificationDestinationData, UpdateNotificationDestinationResponse, UpdateRepositoryData, UpdateRepositoryResponse, UpdateScheduleMirrorsData, UpdateScheduleMirrorsResponse, UpdateScheduleNotificationsData, UpdateScheduleNotificationsResponse, UpdateSsoProviderAutoLinkingData, UpdateVolumeData, UpdateVolumeResponse } from '../types.gen'; export type QueryKey = [ Pick & { @@ -811,6 +811,24 @@ export const getSnapshotDetailsOptions = (options: Options) => createQueryKey('getSnapshotRestorePlan', options); + +/** + * Get a target-scoped restore plan for a snapshot + */ +export const getSnapshotRestorePlanOptions = (options: Options) => queryOptions>({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getSnapshotRestorePlan({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getSnapshotRestorePlanQueryKey(options) +}); + export const listSnapshotFilesQueryKey = (options: Options) => createQueryKey('listSnapshotFiles', options); /** diff --git a/app/client/api-client/client/index.ts b/app/client/api-client/client/index.ts index 2b5d06314..eb37ca93e 100644 --- a/app/client/api-client/client/index.ts +++ b/app/client/api-client/client/index.ts @@ -6,6 +6,8 @@ export type { QuerySerializerOptions } from "../core/bodySerializer.gen"; export { formDataBodySerializer, jsonBodySerializer, urlSearchParamsBodySerializer } from "../core/bodySerializer.gen"; export { buildClientParams } from "../core/params.gen"; export { serializeQueryKeyValue } from "../core/queryKeySerializer.gen"; +export type { ServerSentEventsResult } from "../core/serverSentEvents.gen"; +export type { ClientMeta } from "../core/types.gen"; export { createClient } from "./client.gen"; export type { Client, diff --git a/app/client/api-client/client/utils.gen.ts b/app/client/api-client/client/utils.gen.ts index 26ea50e2c..d8f4d1aea 100644 --- a/app/client/api-client/client/utils.gen.ts +++ b/app/client/api-client/client/utils.gen.ts @@ -15,8 +15,8 @@ import type { Client, ClientOptions, Config, RequestOptions } from './types.gen' export const createQuerySerializer = ({ parameters = {}, ...args -}: QuerySerializerOptions = {}) => { - const querySerializer = (queryParams: T) => { +}: QuerySerializerOptions = {}): ((queryParams: T) => string) => { + const querySerializer = (queryParams: T): string => { const search: string[] = []; if (queryParams && typeof queryParams === 'object') { for (const name in queryParams) { diff --git a/app/client/api-client/core/auth.gen.ts b/app/client/api-client/core/auth.gen.ts index bc5649577..01df2672f 100644 --- a/app/client/api-client/core/auth.gen.ts +++ b/app/client/api-client/core/auth.gen.ts @@ -10,6 +10,13 @@ export interface Auth { * @default 'header' */ in?: 'header' | 'query' | 'cookie'; + /** + * A unique identifier for the security scheme. + * + * Defined only when there are multiple security schemes whose `Auth` + * shape would otherwise be identical. + */ + key?: string; /** * Header or query parameter name. * diff --git a/app/client/api-client/core/params.gen.ts b/app/client/api-client/core/params.gen.ts index 16689776b..6e0250b1e 100644 --- a/app/client/api-client/core/params.gen.ts +++ b/app/client/api-client/core/params.gen.ts @@ -63,7 +63,7 @@ type KeyMap = Map< } >; -const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { +function buildKeyMap(fields: FieldsConfig, map?: KeyMap): KeyMap { if (!map) { map = new Map(); } @@ -86,7 +86,7 @@ const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { } return map; -}; +} interface Params { body: unknown; @@ -95,16 +95,18 @@ interface Params { query: Record; } -const stripEmptySlots = (params: Params) => { +type ParamsSlotMap = Record; + +function stripEmptySlots(params: ParamsSlotMap): void { for (const [slot, value] of Object.entries(params)) { if (value && typeof value === 'object' && !Array.isArray(value) && !Object.keys(value).length) { delete params[slot as Slot]; } } -}; +} -export const buildClientParams = (args: ReadonlyArray, fields: FieldsConfig) => { - const params: Params = { +export function buildClientParams(args: ReadonlyArray, fields: FieldsConfig): Params { + const params: ParamsSlotMap = { body: Object.create(null), headers: Object.create(null), path: Object.create(null), @@ -166,5 +168,5 @@ export const buildClientParams = (args: ReadonlyArray, fields: FieldsCo stripEmptySlots(params); - return params; -}; + return params as Params; +} diff --git a/app/client/api-client/core/pathSerializer.gen.ts b/app/client/api-client/core/pathSerializer.gen.ts index 075a8bc48..f44fc0165 100644 --- a/app/client/api-client/core/pathSerializer.gen.ts +++ b/app/client/api-client/core/pathSerializer.gen.ts @@ -26,7 +26,7 @@ interface SerializePrimitiveParam extends SerializePrimitiveOptions { value: string; } -export const separatorArrayExplode = (style: ArraySeparatorStyle) => { +export const separatorArrayExplode = (style: ArraySeparatorStyle): '.' | ';' | ',' | '&' => { switch (style) { case 'label': return '.'; @@ -39,7 +39,7 @@ export const separatorArrayExplode = (style: ArraySeparatorStyle) => { } }; -export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { +export const separatorArrayNoExplode = (style: ArraySeparatorStyle): ',' | '|' | '%20' => { switch (style) { case 'form': return ','; @@ -52,7 +52,7 @@ export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { } }; -export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { +export const separatorObjectExplode = (style: ObjectSeparatorStyle): '.' | ';' | ',' | '&' => { switch (style) { case 'label': return '.'; @@ -73,7 +73,7 @@ export const serializeArrayParam = ({ value, }: SerializeOptions & { value: unknown[]; -}) => { +}): string => { if (!explode) { const joinedValues = ( allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) @@ -111,7 +111,7 @@ export const serializePrimitiveParam = ({ allowReserved, name, value, -}: SerializePrimitiveParam) => { +}: SerializePrimitiveParam): string => { if (value === undefined || value === null) { return ''; } @@ -135,7 +135,7 @@ export const serializeObjectParam = ({ }: SerializeOptions & { value: Record | Date; valueOnly?: boolean; -}) => { +}): string => { if (value instanceof Date) { return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; } diff --git a/app/client/api-client/core/queryKeySerializer.gen.ts b/app/client/api-client/core/queryKeySerializer.gen.ts index d7ba72b5a..46b83b110 100644 --- a/app/client/api-client/core/queryKeySerializer.gen.ts +++ b/app/client/api-client/core/queryKeySerializer.gen.ts @@ -15,7 +15,7 @@ export type JsonValue = /** * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes. */ -export const queryKeyJsonReplacer = (_key: string, value: unknown) => { +export const queryKeyJsonReplacer = (_key: string, value: unknown): unknown | undefined => { if (value === undefined || typeof value === 'function' || typeof value === 'symbol') { return undefined; } diff --git a/app/client/api-client/core/types.gen.ts b/app/client/api-client/core/types.gen.ts index 8dd55e14e..8df37c8b9 100644 --- a/app/client/api-client/core/types.gen.ts +++ b/app/client/api-client/core/types.gen.ts @@ -92,6 +92,12 @@ export interface Config { responseValidator?: (data: unknown) => Promise; } +/** + * Arbitrary metadata passed through the `meta` request option. + */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface ClientMeta {} + type IsExactlyNeverOrNeverUndefined = [T] extends [never] ? true : [T] extends [never | undefined] diff --git a/app/client/api-client/core/utils.gen.ts b/app/client/api-client/core/utils.gen.ts index 99f215548..093ec64c5 100644 --- a/app/client/api-client/core/utils.gen.ts +++ b/app/client/api-client/core/utils.gen.ts @@ -14,9 +14,9 @@ export interface PathSerializer { url: string; } -export const PATH_PARAM_RE = /\{[^{}]+\}/g; +export const PATH_PARAM_RE: RegExp = /\{[^{}]+\}/g; -export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer): string => { let url = _url; const matches = _url.match(PATH_PARAM_RE); if (matches) { @@ -95,7 +95,7 @@ export const getUrl = ({ query?: Record; querySerializer: QuerySerializer; url: string; -}) => { +}): string => { const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; let url = (baseUrl ?? '') + pathUrl; if (path) { @@ -115,7 +115,7 @@ export function getValidRequestBody(options: { body?: unknown; bodySerializer?: BodySerializer | null; serializedBody?: unknown; -}) { +}): unknown { const hasBody = options.body !== undefined; const isSerializedBody = hasBody && options.bodySerializer; diff --git a/app/client/api-client/index.ts b/app/client/api-client/index.ts index 4e89a099e..e3df53ff1 100644 --- a/app/client/api-client/index.ts +++ b/app/client/api-client/index.ts @@ -41,6 +41,7 @@ export { getScheduleMirrors, getScheduleNotifications, getSnapshotDetails, + getSnapshotRestorePlan, getSsoSettings, getStatus, getSystemInfo, @@ -209,6 +210,9 @@ export type { GetSnapshotDetailsData, GetSnapshotDetailsResponse, GetSnapshotDetailsResponses, + GetSnapshotRestorePlanData, + GetSnapshotRestorePlanResponse, + GetSnapshotRestorePlanResponses, GetSsoSettingsData, GetSsoSettingsResponse, GetSsoSettingsResponses, diff --git a/app/client/api-client/sdk.gen.ts b/app/client/api-client/sdk.gen.ts index c93c43e8f..74b27406c 100644 --- a/app/client/api-client/sdk.gen.ts +++ b/app/client/api-client/sdk.gen.ts @@ -1,9 +1,9 @@ // @ts-nocheck // This file is auto-generated by @hey-api/openapi-ts -import type { Client, Options as Options2, TDataShape } from './client'; +import type { Client, ClientMeta, Options as Options2, RequestResult, ServerSentEventsResult, TDataShape } from './client'; import { client } from './client.gen'; -import type { BrowseFilesystemData, BrowseFilesystemResponses, CancelDoctorData, CancelDoctorErrors, CancelDoctorResponses, CreateApiKeyData, CreateApiKeyErrors, CreateApiKeyResponses, CreateBackupScheduleData, CreateBackupScheduleResponses, CreateDesktopSessionData, CreateDesktopSessionResponses, CreateNotificationDestinationData, CreateNotificationDestinationResponses, CreateRepositoryData, CreateRepositoryResponses, CreateVolumeData, CreateVolumeResponses, DeleteApiKeyData, DeleteApiKeyErrors, DeleteApiKeyResponses, DeleteBackupScheduleData, DeleteBackupScheduleResponses, DeleteNotificationDestinationData, DeleteNotificationDestinationErrors, DeleteNotificationDestinationResponses, DeleteRepositoryData, DeleteRepositoryResponses, DeleteSnapshotData, DeleteSnapshotResponses, DeleteSnapshotsData, DeleteSnapshotsResponses, DeleteSsoInvitationData, DeleteSsoInvitationErrors, DeleteSsoInvitationResponses, DeleteSsoProviderData, DeleteSsoProviderErrors, DeleteSsoProviderResponses, DeleteUserAccountData, DeleteUserAccountErrors, DeleteUserAccountResponses, DeleteVolumeData, DeleteVolumeResponses, DevPanelExecData, DevPanelExecErrors, DevPanelExecResponse, DevPanelExecResponses, DownloadResticPasswordData, DownloadResticPasswordResponses, DumpSnapshotData, DumpSnapshotResponses, GetAdminUsersData, GetAdminUsersResponses, GetApiKeysData, GetApiKeysResponses, GetBackupProgressData, GetBackupProgressResponses, GetBackupScheduleData, GetBackupScheduleForVolumeData, GetBackupScheduleForVolumeResponses, GetBackupScheduleResponses, GetDevPanelData, GetDevPanelResponses, GetMirrorCompatibilityData, GetMirrorCompatibilityResponses, GetMirrorSyncStatusData, GetMirrorSyncStatusResponses, GetNotificationDestinationData, GetNotificationDestinationErrors, GetNotificationDestinationResponses, GetOrgMembersData, GetOrgMembersResponses, GetPasswordLoginStatusData, GetPasswordLoginStatusResponses, GetPublicSsoProvidersData, GetPublicSsoProvidersResponses, GetRegistrationStatusData, GetRegistrationStatusResponses, GetRepositoryData, GetRepositoryResponses, GetRepositoryStatsData, GetRepositoryStatsResponses, GetScheduleMirrorsData, GetScheduleMirrorsResponses, GetScheduleNotificationsData, GetScheduleNotificationsResponses, GetSnapshotDetailsData, GetSnapshotDetailsResponses, GetSsoSettingsData, GetSsoSettingsResponses, GetStatusData, GetStatusResponses, GetSystemInfoData, GetSystemInfoResponses, GetUpdatesData, GetUpdatesResponses, GetUserDeletionImpactData, GetUserDeletionImpactResponses, GetUserSsoInvitationsData, GetUserSsoInvitationsResponses, GetVolumeData, GetVolumeErrors, GetVolumeResponses, HealthCheckVolumeData, HealthCheckVolumeErrors, HealthCheckVolumeResponses, ListBackupSchedulesData, ListBackupSchedulesResponses, ListFilesData, ListFilesResponses, ListNotificationDestinationsData, ListNotificationDestinationsResponses, ListRcloneRemotesData, ListRcloneRemotesResponses, ListRepositoriesData, ListRepositoriesResponses, ListSnapshotFilesData, ListSnapshotFilesResponses, ListSnapshotsData, ListSnapshotsResponses, ListVolumesData, ListVolumesResponses, MountVolumeData, MountVolumeResponses, RefreshRepositoryStatsData, RefreshRepositoryStatsResponses, RefreshSnapshotsData, RefreshSnapshotsResponses, RemoveOrgMemberData, RemoveOrgMemberErrors, RemoveOrgMemberResponses, ReorderBackupSchedulesData, ReorderBackupSchedulesResponses, RestoreSnapshotData, RestoreSnapshotResponses, RunBackupNowData, RunBackupNowResponses, RunForgetData, RunForgetResponses, SetPasswordLoginStatusData, SetPasswordLoginStatusResponses, SetRegistrationStatusData, SetRegistrationStatusResponses, StartDoctorData, StartDoctorErrors, StartDoctorResponses, StartInvitationSsoVerificationData, StartInvitationSsoVerificationErrors, StartInvitationSsoVerificationResponses, StopBackupData, StopBackupErrors, StopBackupResponses, SyncMirrorData, SyncMirrorErrors, SyncMirrorResponses, TagSnapshotsData, TagSnapshotsResponses, TestConnectionData, TestConnectionResponses, TestNotificationDestinationData, TestNotificationDestinationErrors, TestNotificationDestinationResponses, UnlockRepositoryData, UnlockRepositoryResponses, UnmountVolumeData, UnmountVolumeResponses, UpdateBackupScheduleData, UpdateBackupScheduleResponses, UpdateMemberRoleData, UpdateMemberRoleErrors, UpdateMemberRoleResponses, UpdateNotificationDestinationData, UpdateNotificationDestinationErrors, UpdateNotificationDestinationResponses, UpdateRepositoryData, UpdateRepositoryErrors, UpdateRepositoryResponses, UpdateScheduleMirrorsData, UpdateScheduleMirrorsResponses, UpdateScheduleNotificationsData, UpdateScheduleNotificationsResponses, UpdateSsoProviderAutoLinkingData, UpdateSsoProviderAutoLinkingErrors, UpdateSsoProviderAutoLinkingResponses, UpdateVolumeData, UpdateVolumeErrors, UpdateVolumeResponses } from './types.gen'; +import type { BrowseFilesystemData, BrowseFilesystemResponses, CancelDoctorData, CancelDoctorErrors, CancelDoctorResponses, CreateApiKeyData, CreateApiKeyErrors, CreateApiKeyResponses, CreateBackupScheduleData, CreateBackupScheduleResponses, CreateDesktopSessionData, CreateDesktopSessionResponses, CreateNotificationDestinationData, CreateNotificationDestinationResponses, CreateRepositoryData, CreateRepositoryResponses, CreateVolumeData, CreateVolumeResponses, DeleteApiKeyData, DeleteApiKeyErrors, DeleteApiKeyResponses, DeleteBackupScheduleData, DeleteBackupScheduleResponses, DeleteNotificationDestinationData, DeleteNotificationDestinationErrors, DeleteNotificationDestinationResponses, DeleteRepositoryData, DeleteRepositoryResponses, DeleteSnapshotData, DeleteSnapshotResponses, DeleteSnapshotsData, DeleteSnapshotsResponses, DeleteSsoInvitationData, DeleteSsoInvitationErrors, DeleteSsoInvitationResponses, DeleteSsoProviderData, DeleteSsoProviderErrors, DeleteSsoProviderResponses, DeleteUserAccountData, DeleteUserAccountErrors, DeleteUserAccountResponses, DeleteVolumeData, DeleteVolumeResponses, DevPanelExecData, DevPanelExecErrors, DevPanelExecResponse, DevPanelExecResponses, DownloadResticPasswordData, DownloadResticPasswordResponses, DumpSnapshotData, DumpSnapshotResponses, GetAdminUsersData, GetAdminUsersResponses, GetApiKeysData, GetApiKeysResponses, GetBackupProgressData, GetBackupProgressResponses, GetBackupScheduleData, GetBackupScheduleForVolumeData, GetBackupScheduleForVolumeResponses, GetBackupScheduleResponses, GetDevPanelData, GetDevPanelResponses, GetMirrorCompatibilityData, GetMirrorCompatibilityResponses, GetMirrorSyncStatusData, GetMirrorSyncStatusResponses, GetNotificationDestinationData, GetNotificationDestinationErrors, GetNotificationDestinationResponses, GetOrgMembersData, GetOrgMembersResponses, GetPasswordLoginStatusData, GetPasswordLoginStatusResponses, GetPublicSsoProvidersData, GetPublicSsoProvidersResponses, GetRegistrationStatusData, GetRegistrationStatusResponses, GetRepositoryData, GetRepositoryResponses, GetRepositoryStatsData, GetRepositoryStatsResponses, GetScheduleMirrorsData, GetScheduleMirrorsResponses, GetScheduleNotificationsData, GetScheduleNotificationsResponses, GetSnapshotDetailsData, GetSnapshotDetailsResponses, GetSnapshotRestorePlanData, GetSnapshotRestorePlanResponses, GetSsoSettingsData, GetSsoSettingsResponses, GetStatusData, GetStatusResponses, GetSystemInfoData, GetSystemInfoResponses, GetUpdatesData, GetUpdatesResponses, GetUserDeletionImpactData, GetUserDeletionImpactResponses, GetUserSsoInvitationsData, GetUserSsoInvitationsResponses, GetVolumeData, GetVolumeErrors, GetVolumeResponses, HealthCheckVolumeData, HealthCheckVolumeErrors, HealthCheckVolumeResponses, ListBackupSchedulesData, ListBackupSchedulesResponses, ListFilesData, ListFilesResponses, ListNotificationDestinationsData, ListNotificationDestinationsResponses, ListRcloneRemotesData, ListRcloneRemotesResponses, ListRepositoriesData, ListRepositoriesResponses, ListSnapshotFilesData, ListSnapshotFilesResponses, ListSnapshotsData, ListSnapshotsResponses, ListVolumesData, ListVolumesResponses, MountVolumeData, MountVolumeResponses, RefreshRepositoryStatsData, RefreshRepositoryStatsResponses, RefreshSnapshotsData, RefreshSnapshotsResponses, RemoveOrgMemberData, RemoveOrgMemberErrors, RemoveOrgMemberResponses, ReorderBackupSchedulesData, ReorderBackupSchedulesResponses, RestoreSnapshotData, RestoreSnapshotResponses, RunBackupNowData, RunBackupNowResponses, RunForgetData, RunForgetResponses, SetPasswordLoginStatusData, SetPasswordLoginStatusResponses, SetRegistrationStatusData, SetRegistrationStatusResponses, StartDoctorData, StartDoctorErrors, StartDoctorResponses, StartInvitationSsoVerificationData, StartInvitationSsoVerificationErrors, StartInvitationSsoVerificationResponses, StopBackupData, StopBackupErrors, StopBackupResponses, SyncMirrorData, SyncMirrorErrors, SyncMirrorResponses, TagSnapshotsData, TagSnapshotsResponses, TestConnectionData, TestConnectionResponses, TestNotificationDestinationData, TestNotificationDestinationErrors, TestNotificationDestinationResponses, UnlockRepositoryData, UnlockRepositoryResponses, UnmountVolumeData, UnmountVolumeResponses, UpdateBackupScheduleData, UpdateBackupScheduleResponses, UpdateMemberRoleData, UpdateMemberRoleErrors, UpdateMemberRoleResponses, UpdateNotificationDestinationData, UpdateNotificationDestinationErrors, UpdateNotificationDestinationResponses, UpdateRepositoryData, UpdateRepositoryErrors, UpdateRepositoryResponses, UpdateScheduleMirrorsData, UpdateScheduleMirrorsResponses, UpdateScheduleNotificationsData, UpdateScheduleNotificationsResponses, UpdateSsoProviderAutoLinkingData, UpdateSsoProviderAutoLinkingErrors, UpdateSsoProviderAutoLinkingResponses, UpdateVolumeData, UpdateVolumeErrors, UpdateVolumeResponses } from './types.gen'; export type Options = Options2 & { /** @@ -16,38 +16,38 @@ export type Options; + meta?: keyof ClientMeta extends never ? Record : ClientMeta; }; /** * Get authentication system status */ -export const getStatus = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/auth/status', ...options }); +export const getStatus = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/auth/status', ...options }); /** * List admin users for settings management */ -export const getAdminUsers = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/auth/admin-users', ...options }); +export const getAdminUsers = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/auth/admin-users', ...options }); /** * Delete an account linked to a user */ -export const deleteUserAccount = (options: Options) => (options.client ?? client).delete({ url: '/api/v1/auth/admin-users/{userId}/accounts/{accountId}', ...options }); +export const deleteUserAccount = (options: Options): RequestResult => (options.client ?? client).delete({ url: '/api/v1/auth/admin-users/{userId}/accounts/{accountId}', ...options }); /** * Get impact of deleting a user */ -export const getUserDeletionImpact = (options: Options) => (options.client ?? client).get({ url: '/api/v1/auth/deletion-impact/{userId}', ...options }); +export const getUserDeletionImpact = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/auth/deletion-impact/{userId}', ...options }); /** * Get members of the active organization */ -export const getOrgMembers = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/auth/org-members', ...options }); +export const getOrgMembers = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/auth/org-members', ...options }); /** * Update a member's role in the active organization */ -export const updateMemberRole = (options: Options) => (options.client ?? client).patch({ +export const updateMemberRole = (options: Options): RequestResult => (options.client ?? client).patch({ url: '/api/v1/auth/org-members/{memberId}/role', ...options, headers: { @@ -59,17 +59,17 @@ export const updateMemberRole = (options: /** * Remove a member from the active organization */ -export const removeOrgMember = (options: Options) => (options.client ?? client).delete({ url: '/api/v1/auth/org-members/{memberId}', ...options }); +export const removeOrgMember = (options: Options): RequestResult => (options.client ?? client).delete({ url: '/api/v1/auth/org-members/{memberId}', ...options }); /** * List API keys for the current user in the active organization */ -export const getApiKeys = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/auth/api-keys', ...options }); +export const getApiKeys = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/auth/api-keys', ...options }); /** * Create an API key for the current user in the active organization */ -export const createApiKey = (options: Options) => (options.client ?? client).post({ +export const createApiKey = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/auth/api-keys', ...options, headers: { @@ -81,27 +81,27 @@ export const createApiKey = (options: Opti /** * Revoke an API key for the current user in the active organization */ -export const deleteApiKey = (options: Options) => (options.client ?? client).delete({ url: '/api/v1/auth/api-keys/{keyId}', ...options }); +export const deleteApiKey = (options: Options): RequestResult => (options.client ?? client).delete({ url: '/api/v1/auth/api-keys/{keyId}', ...options }); /** * Get public SSO providers for the instance */ -export const getPublicSsoProviders = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/auth/sso-providers', ...options }); +export const getPublicSsoProviders = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/auth/sso-providers', ...options }); /** * Get SSO providers and invitations for the active organization */ -export const getSsoSettings = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/auth/sso-settings', ...options }); +export const getSsoSettings = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/auth/sso-settings', ...options }); /** * Get pending SSO invitations for the authenticated user */ -export const getUserSsoInvitations = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/auth/sso-invitations', ...options }); +export const getUserSsoInvitations = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/auth/sso-invitations', ...options }); /** * Start SSO verification for accepting an organization invitation */ -export const startInvitationSsoVerification = (options: Options) => (options.client ?? client).post({ +export const startInvitationSsoVerification = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/auth/sso-invitations/{invitationId}/verify', ...options, headers: { @@ -113,12 +113,12 @@ export const startInvitationSsoVerification = (options: Options) => (options.client ?? client).delete({ url: '/api/v1/auth/sso-providers/{providerId}', ...options }); +export const deleteSsoProvider = (options: Options): RequestResult => (options.client ?? client).delete({ url: '/api/v1/auth/sso-providers/{providerId}', ...options }); /** * Update whether SSO sign-in can auto-link existing accounts by email */ -export const updateSsoProviderAutoLinking = (options: Options) => (options.client ?? client).patch({ +export const updateSsoProviderAutoLinking = (options: Options): RequestResult => (options.client ?? client).patch({ url: '/api/v1/auth/sso-providers/{providerId}/auto-linking', ...options, headers: { @@ -130,17 +130,17 @@ export const updateSsoProviderAutoLinking = (options: Options) => (options.client ?? client).delete({ url: '/api/v1/auth/sso-invitations/{invitationId}', ...options }); +export const deleteSsoInvitation = (options: Options): RequestResult => (options.client ?? client).delete({ url: '/api/v1/auth/sso-invitations/{invitationId}', ...options }); /** * List all volumes */ -export const listVolumes = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/volumes', ...options }); +export const listVolumes = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/volumes', ...options }); /** * Create a new volume */ -export const createVolume = (options: Options) => (options.client ?? client).post({ +export const createVolume = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/volumes', ...options, headers: { @@ -152,7 +152,7 @@ export const createVolume = (options: Opti /** * Test connection to backend */ -export const testConnection = (options: Options) => (options.client ?? client).post({ +export const testConnection = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/volumes/test-connection', ...options, headers: { @@ -164,17 +164,17 @@ export const testConnection = (options: Op /** * Delete a volume */ -export const deleteVolume = (options: Options) => (options.client ?? client).delete({ url: '/api/v1/volumes/{shortId}', ...options }); +export const deleteVolume = (options: Options): RequestResult => (options.client ?? client).delete({ url: '/api/v1/volumes/{shortId}', ...options }); /** * Get a volume by name */ -export const getVolume = (options: Options) => (options.client ?? client).get({ url: '/api/v1/volumes/{shortId}', ...options }); +export const getVolume = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/volumes/{shortId}', ...options }); /** * Update a volume's configuration */ -export const updateVolume = (options: Options) => (options.client ?? client).put({ +export const updateVolume = (options: Options): RequestResult => (options.client ?? client).put({ url: '/api/v1/volumes/{shortId}', ...options, headers: { @@ -186,37 +186,37 @@ export const updateVolume = (options: Opti /** * Mount a volume */ -export const mountVolume = (options: Options) => (options.client ?? client).post({ url: '/api/v1/volumes/{shortId}/mount', ...options }); +export const mountVolume = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/volumes/{shortId}/mount', ...options }); /** * Unmount a volume */ -export const unmountVolume = (options: Options) => (options.client ?? client).post({ url: '/api/v1/volumes/{shortId}/unmount', ...options }); +export const unmountVolume = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/volumes/{shortId}/unmount', ...options }); /** * Perform a health check on a volume */ -export const healthCheckVolume = (options: Options) => (options.client ?? client).post({ url: '/api/v1/volumes/{shortId}/health-check', ...options }); +export const healthCheckVolume = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/volumes/{shortId}/health-check', ...options }); /** * List files in a volume directory */ -export const listFiles = (options: Options) => (options.client ?? client).get({ url: '/api/v1/volumes/{shortId}/files', ...options }); +export const listFiles = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/volumes/{shortId}/files', ...options }); /** * Browse directories on the host filesystem */ -export const browseFilesystem = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/volumes/filesystem/browse', ...options }); +export const browseFilesystem = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/volumes/filesystem/browse', ...options }); /** * List all repositories */ -export const listRepositories = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/repositories', ...options }); +export const listRepositories = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/repositories', ...options }); /** * Create a new restic repository */ -export const createRepository = (options: Options) => (options.client ?? client).post({ +export const createRepository = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/repositories', ...options, headers: { @@ -228,22 +228,22 @@ export const createRepository = (options: /** * List all configured rclone remotes on the host system */ -export const listRcloneRemotes = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/repositories/rclone-remotes', ...options }); +export const listRcloneRemotes = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/repositories/rclone-remotes', ...options }); /** * Delete a repository */ -export const deleteRepository = (options: Options) => (options.client ?? client).delete({ url: '/api/v1/repositories/{shortId}', ...options }); +export const deleteRepository = (options: Options): RequestResult => (options.client ?? client).delete({ url: '/api/v1/repositories/{shortId}', ...options }); /** * Get a single repository by ID */ -export const getRepository = (options: Options) => (options.client ?? client).get({ url: '/api/v1/repositories/{shortId}', ...options }); +export const getRepository = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/repositories/{shortId}', ...options }); /** * Update a repository's name or settings */ -export const updateRepository = (options: Options) => (options.client ?? client).patch({ +export const updateRepository = (options: Options): RequestResult => (options.client ?? client).patch({ url: '/api/v1/repositories/{shortId}', ...options, headers: { @@ -255,17 +255,17 @@ export const updateRepository = (options: /** * Get repository storage and compression statistics */ -export const getRepositoryStats = (options: Options) => (options.client ?? client).get({ url: '/api/v1/repositories/{shortId}/stats', ...options }); +export const getRepositoryStats = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/repositories/{shortId}/stats', ...options }); /** * Refresh repository storage and compression statistics */ -export const refreshRepositoryStats = (options: Options) => (options.client ?? client).post({ url: '/api/v1/repositories/{shortId}/stats/refresh', ...options }); +export const refreshRepositoryStats = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/repositories/{shortId}/stats/refresh', ...options }); /** * Delete multiple snapshots from a repository */ -export const deleteSnapshots = (options: Options) => (options.client ?? client).delete({ +export const deleteSnapshots = (options: Options): RequestResult => (options.client ?? client).delete({ url: '/api/v1/repositories/{shortId}/snapshots', ...options, headers: { @@ -277,37 +277,42 @@ export const deleteSnapshots = (options: O /** * List all snapshots in a repository */ -export const listSnapshots = (options: Options) => (options.client ?? client).get({ url: '/api/v1/repositories/{shortId}/snapshots', ...options }); +export const listSnapshots = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/repositories/{shortId}/snapshots', ...options }); /** * Clear snapshot cache and force refresh from repository */ -export const refreshSnapshots = (options: Options) => (options.client ?? client).post({ url: '/api/v1/repositories/{shortId}/snapshots/refresh', ...options }); +export const refreshSnapshots = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/repositories/{shortId}/snapshots/refresh', ...options }); /** * Delete a specific snapshot from a repository */ -export const deleteSnapshot = (options: Options) => (options.client ?? client).delete({ url: '/api/v1/repositories/{shortId}/snapshots/{snapshotId}', ...options }); +export const deleteSnapshot = (options: Options): RequestResult => (options.client ?? client).delete({ url: '/api/v1/repositories/{shortId}/snapshots/{snapshotId}', ...options }); /** * Get details of a specific snapshot */ -export const getSnapshotDetails = (options: Options) => (options.client ?? client).get({ url: '/api/v1/repositories/{shortId}/snapshots/{snapshotId}', ...options }); +export const getSnapshotDetails = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/repositories/{shortId}/snapshots/{snapshotId}', ...options }); + +/** + * Get a target-scoped restore plan for a snapshot + */ +export const getSnapshotRestorePlan = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/repositories/{shortId}/snapshots/{snapshotId}/restore-plan', ...options }); /** * List files and directories in a snapshot */ -export const listSnapshotFiles = (options: Options) => (options.client ?? client).get({ url: '/api/v1/repositories/{shortId}/snapshots/{snapshotId}/files', ...options }); +export const listSnapshotFiles = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/repositories/{shortId}/snapshots/{snapshotId}/files', ...options }); /** * Download a snapshot path as a tar archive (folders) or raw file stream (single files) */ -export const dumpSnapshot = (options: Options) => (options.client ?? client).get({ url: '/api/v1/repositories/{shortId}/snapshots/{snapshotId}/dump', ...options }); +export const dumpSnapshot = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/repositories/{shortId}/snapshots/{snapshotId}/dump', ...options }); /** * Restore a snapshot to a target path on the filesystem */ -export const restoreSnapshot = (options: Options) => (options.client ?? client).post({ +export const restoreSnapshot = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/repositories/{shortId}/restore', ...options, headers: { @@ -319,22 +324,22 @@ export const restoreSnapshot = (options: O /** * Cancel a running doctor operation on a repository */ -export const cancelDoctor = (options: Options) => (options.client ?? client).delete({ url: '/api/v1/repositories/{shortId}/doctor', ...options }); +export const cancelDoctor = (options: Options): RequestResult => (options.client ?? client).delete({ url: '/api/v1/repositories/{shortId}/doctor', ...options }); /** * Start an asynchronous doctor operation on a repository to fix common issues (unlock, check, repair index). The operation runs in the background and sends results via SSE events. */ -export const startDoctor = (options: Options) => (options.client ?? client).post({ url: '/api/v1/repositories/{shortId}/doctor', ...options }); +export const startDoctor = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/repositories/{shortId}/doctor', ...options }); /** * Unlock a repository by removing all stale locks */ -export const unlockRepository = (options: Options) => (options.client ?? client).post({ url: '/api/v1/repositories/{shortId}/unlock', ...options }); +export const unlockRepository = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/repositories/{shortId}/unlock', ...options }); /** * Tag multiple snapshots in a repository */ -export const tagSnapshots = (options: Options) => (options.client ?? client).post({ +export const tagSnapshots = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/repositories/{shortId}/snapshots/tag', ...options, headers: { @@ -346,7 +351,7 @@ export const tagSnapshots = (options: Opti /** * Execute a restic command against a repository (dev panel only) */ -export const devPanelExec = (options: Options) => (options.client ?? client).sse.post({ +export const devPanelExec = (options: Options): Promise> => (options.client ?? client).sse.post({ url: '/api/v1/repositories/{shortId}/exec', ...options, headers: { @@ -358,12 +363,12 @@ export const devPanelExec = (options: Opti /** * List all backup schedules */ -export const listBackupSchedules = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/backups', ...options }); +export const listBackupSchedules = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/backups', ...options }); /** * Create a new backup schedule for a volume */ -export const createBackupSchedule = (options: Options) => (options.client ?? client).post({ +export const createBackupSchedule = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/backups', ...options, headers: { @@ -375,17 +380,17 @@ export const createBackupSchedule = (optio /** * Delete a backup schedule */ -export const deleteBackupSchedule = (options: Options) => (options.client ?? client).delete({ url: '/api/v1/backups/{shortId}', ...options }); +export const deleteBackupSchedule = (options: Options): RequestResult => (options.client ?? client).delete({ url: '/api/v1/backups/{shortId}', ...options }); /** * Get a backup schedule by ID */ -export const getBackupSchedule = (options: Options) => (options.client ?? client).get({ url: '/api/v1/backups/{shortId}', ...options }); +export const getBackupSchedule = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/backups/{shortId}', ...options }); /** * Update a backup schedule */ -export const updateBackupSchedule = (options: Options) => (options.client ?? client).patch({ +export const updateBackupSchedule = (options: Options): RequestResult => (options.client ?? client).patch({ url: '/api/v1/backups/{shortId}', ...options, headers: { @@ -397,32 +402,32 @@ export const updateBackupSchedule = (optio /** * Get a backup schedule for a specific volume */ -export const getBackupScheduleForVolume = (options: Options) => (options.client ?? client).get({ url: '/api/v1/backups/volume/{volumeShortId}', ...options }); +export const getBackupScheduleForVolume = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/backups/volume/{volumeShortId}', ...options }); /** * Trigger a backup immediately for a schedule */ -export const runBackupNow = (options: Options) => (options.client ?? client).post({ url: '/api/v1/backups/{shortId}/run', ...options }); +export const runBackupNow = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/backups/{shortId}/run', ...options }); /** * Stop a backup that is currently in progress */ -export const stopBackup = (options: Options) => (options.client ?? client).post({ url: '/api/v1/backups/{shortId}/stop', ...options }); +export const stopBackup = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/backups/{shortId}/stop', ...options }); /** * Manually apply retention policy to clean up old snapshots */ -export const runForget = (options: Options) => (options.client ?? client).post({ url: '/api/v1/backups/{shortId}/forget', ...options }); +export const runForget = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/backups/{shortId}/forget', ...options }); /** * Get notification assignments for a backup schedule */ -export const getScheduleNotifications = (options: Options) => (options.client ?? client).get({ url: '/api/v1/backups/{shortId}/notifications', ...options }); +export const getScheduleNotifications = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/backups/{shortId}/notifications', ...options }); /** * Update notification assignments for a backup schedule */ -export const updateScheduleNotifications = (options: Options) => (options.client ?? client).put({ +export const updateScheduleNotifications = (options: Options): RequestResult => (options.client ?? client).put({ url: '/api/v1/backups/{shortId}/notifications', ...options, headers: { @@ -434,12 +439,12 @@ export const updateScheduleNotifications = (options: Options) => (options.client ?? client).get({ url: '/api/v1/backups/{shortId}/mirrors', ...options }); +export const getScheduleMirrors = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/backups/{shortId}/mirrors', ...options }); /** * Update mirror repository assignments for a backup schedule */ -export const updateScheduleMirrors = (options: Options) => (options.client ?? client).put({ +export const updateScheduleMirrors = (options: Options): RequestResult => (options.client ?? client).put({ url: '/api/v1/backups/{shortId}/mirrors', ...options, headers: { @@ -451,12 +456,12 @@ export const updateScheduleMirrors = (opti /** * Get sync status for a specific mirror, including missing snapshots */ -export const getMirrorSyncStatus = (options: Options) => (options.client ?? client).get({ url: '/api/v1/backups/{shortId}/mirrors/{mirrorShortId}/status', ...options }); +export const getMirrorSyncStatus = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/backups/{shortId}/mirrors/{mirrorShortId}/status', ...options }); /** * Sync selected snapshots to a specific mirror repository */ -export const syncMirror = (options: Options) => (options.client ?? client).post({ +export const syncMirror = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/backups/{shortId}/mirrors/{mirrorShortId}/sync', ...options, headers: { @@ -468,12 +473,12 @@ export const syncMirror = (options: Option /** * Get mirror compatibility info for all repositories relative to a backup schedule's primary repository */ -export const getMirrorCompatibility = (options: Options) => (options.client ?? client).get({ url: '/api/v1/backups/{shortId}/mirrors/compatibility', ...options }); +export const getMirrorCompatibility = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/backups/{shortId}/mirrors/compatibility', ...options }); /** * Reorder backup schedules by providing an array of schedule short IDs in the desired order */ -export const reorderBackupSchedules = (options: Options) => (options.client ?? client).post({ +export const reorderBackupSchedules = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/backups/reorder', ...options, headers: { @@ -485,17 +490,17 @@ export const reorderBackupSchedules = (opt /** * Get the last known progress for a currently running backup. Returns null if no progress has been reported yet. */ -export const getBackupProgress = (options: Options) => (options.client ?? client).get({ url: '/api/v1/backups/{shortId}/progress', ...options }); +export const getBackupProgress = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/backups/{shortId}/progress', ...options }); /** * List all notification destinations */ -export const listNotificationDestinations = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/notifications/destinations', ...options }); +export const listNotificationDestinations = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/notifications/destinations', ...options }); /** * Create a new notification destination */ -export const createNotificationDestination = (options: Options) => (options.client ?? client).post({ +export const createNotificationDestination = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/notifications/destinations', ...options, headers: { @@ -507,17 +512,17 @@ export const createNotificationDestination = (options: Options) => (options.client ?? client).delete({ url: '/api/v1/notifications/destinations/{id}', ...options }); +export const deleteNotificationDestination = (options: Options): RequestResult => (options.client ?? client).delete({ url: '/api/v1/notifications/destinations/{id}', ...options }); /** * Get a notification destination by ID */ -export const getNotificationDestination = (options: Options) => (options.client ?? client).get({ url: '/api/v1/notifications/destinations/{id}', ...options }); +export const getNotificationDestination = (options: Options): RequestResult => (options.client ?? client).get({ url: '/api/v1/notifications/destinations/{id}', ...options }); /** * Update a notification destination */ -export const updateNotificationDestination = (options: Options) => (options.client ?? client).patch({ +export const updateNotificationDestination = (options: Options): RequestResult => (options.client ?? client).patch({ url: '/api/v1/notifications/destinations/{id}', ...options, headers: { @@ -529,27 +534,27 @@ export const updateNotificationDestination = (options: Options) => (options.client ?? client).post({ url: '/api/v1/notifications/destinations/{id}/test', ...options }); +export const testNotificationDestination = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/notifications/destinations/{id}/test', ...options }); /** * Get system information including available capabilities */ -export const getSystemInfo = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/system/info', ...options }); +export const getSystemInfo = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/system/info', ...options }); /** * Check for application updates from GitHub */ -export const getUpdates = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/system/updates', ...options }); +export const getUpdates = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/system/updates', ...options }); /** * Get the current registration status for new users */ -export const getRegistrationStatus = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/system/registration-status', ...options }); +export const getRegistrationStatus = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/system/registration-status', ...options }); /** * Update the registration status for new users. Requires global admin role. */ -export const setRegistrationStatus = (options: Options) => (options.client ?? client).put({ +export const setRegistrationStatus = (options: Options): RequestResult => (options.client ?? client).put({ url: '/api/v1/system/registration-status', ...options, headers: { @@ -561,7 +566,7 @@ export const setRegistrationStatus = (opti /** * Download the organization's Restic password for backup recovery. Requires organization owner or admin role and may require password re-authentication. */ -export const downloadResticPassword = (options: Options) => (options.client ?? client).post({ +export const downloadResticPassword = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/system/restic-password', ...options, headers: { @@ -573,12 +578,12 @@ export const downloadResticPassword = (opt /** * Get whether password-based login is disabled */ -export const getPasswordLoginStatus = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/system/password-login-status', ...options }); +export const getPasswordLoginStatus = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/system/password-login-status', ...options }); /** * Disable or re-enable password-based login. Requires global admin role. */ -export const setPasswordLoginStatus = (options: Options) => (options.client ?? client).put({ +export const setPasswordLoginStatus = (options: Options): RequestResult => (options.client ?? client).put({ url: '/api/v1/system/password-login-status', ...options, headers: { @@ -590,12 +595,12 @@ export const setPasswordLoginStatus = (opt /** * Get the dev panel status */ -export const getDevPanel = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/system/dev-panel', ...options }); +export const getDevPanel = (options?: Options): RequestResult => (options?.client ?? client).get({ url: '/api/v1/system/dev-panel', ...options }); /** * Create an authenticated desktop session */ -export const createDesktopSession = (options: Options) => (options.client ?? client).post({ +export const createDesktopSession = (options: Options): RequestResult => (options.client ?? client).post({ url: '/api/v1/desktop/session', ...options, headers: { diff --git a/app/client/api-client/types.gen.ts b/app/client/api-client/types.gen.ts index 1002d0e31..de5dc330d 100644 --- a/app/client/api-client/types.gen.ts +++ b/app/client/api-client/types.gen.ts @@ -2336,6 +2336,30 @@ export type GetSnapshotDetailsResponses = { export type GetSnapshotDetailsResponse = GetSnapshotDetailsResponses[keyof GetSnapshotDetailsResponses]; +export type GetSnapshotRestorePlanData = { + body?: never; + path: { + shortId: string; + snapshotId: string; + }; + query?: { + targetAgentId?: string; + }; + url: '/api/v1/repositories/{shortId}/snapshots/{snapshotId}/restore-plan'; +}; + +export type GetSnapshotRestorePlanResponses = { + /** + * Target-scoped snapshot restore plan + */ + 200: { + queryBasePath: string; + requiresCustomTarget: boolean; + }; +}; + +export type GetSnapshotRestorePlanResponse = GetSnapshotRestorePlanResponses[keyof GetSnapshotRestorePlanResponses]; + export type ListSnapshotFilesData = { body?: never; path: { diff --git a/app/server/modules/repositories/__tests__/repositories.controller.test.ts b/app/server/modules/repositories/__tests__/repositories.controller.test.ts index 9a123cd40..73645925d 100644 --- a/app/server/modules/repositories/__tests__/repositories.controller.test.ts +++ b/app/server/modules/repositories/__tests__/repositories.controller.test.ts @@ -121,6 +121,7 @@ describe("repositories security", () => { { method: "GET", path: "/api/v1/repositories/test-repo/snapshots" }, { method: "POST", path: "/api/v1/repositories/test-repo/snapshots/refresh" }, { method: "GET", path: "/api/v1/repositories/test-repo/snapshots/test-snapshot" }, + { method: "GET", path: "/api/v1/repositories/test-repo/snapshots/test-snapshot/restore-plan" }, { method: "GET", path: "/api/v1/repositories/test-repo/snapshots/test-snapshot/files" }, { method: "GET", path: "/api/v1/repositories/test-repo/snapshots/test-snapshot/dump" }, { method: "POST", path: "/api/v1/repositories/test-repo/restore" }, @@ -143,6 +144,25 @@ describe("repositories security", () => { } }); + test("GET restore-plan returns a plan and passes targetAgentId through", async () => { + const restorePlanSpy = vi.spyOn(repositoriesService, "getSnapshotRestorePlan").mockResolvedValue({ + queryBasePath: "/C/Users/nicolas", + requiresCustomTarget: false, + }); + + const res = await app.request( + "/api/v1/repositories/repo-1/snapshots/snapshot-1/restore-plan?targetAgentId=agent-1", + { headers: session.headers }, + ); + + expect(res.status).toBe(200); + expect(await res.json()).toEqual({ + queryBasePath: "/C/Users/nicolas", + requiresCustomTarget: false, + }); + expect(restorePlanSpy).toHaveBeenCalledWith("repo-1", "snapshot-1", { targetAgentId: "agent-1" }); + }); + describe("dev panel endpoint - requires dev panel access", () => { test("POST /api/v1/repositories/:shortId/exec should return 401 when unauthenticated", async () => { const res = await app.request("/api/v1/repositories/test-repo/exec", { diff --git a/app/server/modules/repositories/repositories.controller.ts b/app/server/modules/repositories/repositories.controller.ts index 66f656d53..d2852f687 100644 --- a/app/server/modules/repositories/repositories.controller.ts +++ b/app/server/modules/repositories/repositories.controller.ts @@ -16,6 +16,8 @@ import { getRepositoryStatsDto, refreshRepositoryStatsDto, getSnapshotDetailsDto, + getSnapshotRestorePlanDto, + getSnapshotRestorePlanQuery, refreshSnapshotsDto, listRcloneRemotesDto, listRepositoriesDto, @@ -43,6 +45,7 @@ import { type GetRepositoryStatsDto, type RefreshRepositoryStatsDto, type GetSnapshotDetailsDto, + type GetSnapshotRestorePlanDto, type RefreshSnapshotsDto, type ListRepositoriesDto, type ListSnapshotFilesDto, @@ -168,6 +171,19 @@ export const repositoriesController = new Hono() return c.json(response, 200); }) + .get( + "/:shortId/snapshots/:snapshotId/restore-plan", + getSnapshotRestorePlanDto, + validator("query", getSnapshotRestorePlanQuery), + async (c) => { + const shortId = asShortId(c.req.param("shortId")); + const snapshotId = c.req.param("snapshotId"); + const query = c.req.valid("query"); + const result = await repositoriesService.getSnapshotRestorePlan(shortId, snapshotId, query); + + return c.json(result, 200); + }, + ) .get( "/:shortId/snapshots/:snapshotId/files", listSnapshotFilesDto, diff --git a/app/server/modules/repositories/repositories.dto.ts b/app/server/modules/repositories/repositories.dto.ts index d0704cccf..594ee7b9a 100644 --- a/app/server/modules/repositories/repositories.dto.ts +++ b/app/server/modules/repositories/repositories.dto.ts @@ -205,6 +205,15 @@ const snapshotSchema = z.object({ summary: resticSnapshotSummarySchema.optional(), }); +const snapshotSourcePathPlanSchema = z.object({ + queryBasePath: z.string(), + requiresCustomTarget: z.boolean(), +}); + +export const getSnapshotRestorePlanQuery = z.object({ + targetAgentId: z.string().optional(), +}); + const listSnapshotsResponse = snapshotSchema.array(); export type ListSnapshotsDto = z.infer; @@ -249,6 +258,35 @@ export const getSnapshotDetailsDto = describeRoute({ }, }); +const getSnapshotRestorePlanResponse = snapshotSourcePathPlanSchema; + +export type GetSnapshotRestorePlanDto = z.infer; + +export const getSnapshotRestorePlanDto = describeRoute({ + description: "Get a target-scoped restore plan for a snapshot", + tags: ["Repositories"], + operationId: "getSnapshotRestorePlan", + parameters: [ + { + in: "query", + name: "targetAgentId", + required: false, + schema: { type: "string" }, + description: "Agent that will execute the restore. Defaults to the local agent.", + }, + ], + responses: { + 200: { + description: "Target-scoped snapshot restore plan", + content: { + "application/json": { + schema: resolver(getSnapshotRestorePlanResponse), + }, + }, + }, + }, +}); + const snapshotFileNodeSchema = z.object({ name: z.string(), type: z.string(),