From 99932a8522edeab0641b8d1de3bfa70d7557fb3b Mon Sep 17 00:00:00 2001 From: Nico <47644445+nicotsx@users.noreply.github.com> Date: Wed, 7 Jan 2026 22:36:20 +0100 Subject: [PATCH] refactor: better-auth (#319) * refactor: better-auth * chore: pr feedback * chore: lower + trim usernames in db --- .dockerignore | 1 + .gitignore | 1 + .../api-client/@tanstack/react-query.gen.ts | 2032 ++++-- app/client/api-client/client.gen.ts | 17 +- app/client/api-client/client/client.gen.ts | 510 +- app/client/api-client/client/index.ts | 42 +- app/client/api-client/client/types.gen.ts | 358 +- app/client/api-client/client/utils.gen.ts | 505 +- app/client/api-client/core/auth.gen.ts | 56 +- .../api-client/core/bodySerializer.gen.ts | 132 +- app/client/api-client/core/params.gen.ts | 272 +- .../api-client/core/pathSerializer.gen.ts | 274 +- .../api-client/core/queryKeySerializer.gen.ts | 176 +- .../api-client/core/serverSentEvents.gen.ts | 444 +- app/client/api-client/core/types.gen.ts | 200 +- app/client/api-client/core/utils.gen.ts | 220 +- app/client/api-client/index.ts | 4 +- app/client/api-client/sdk.gen.ts | 868 ++- app/client/api-client/types.gen.ts | 6128 +++++++++-------- app/client/components/layout.tsx | 27 +- app/client/lib/auth-client.ts | 8 + app/client/lib/types.ts | 3 - app/client/modules/auth/routes/login.tsx | 54 +- app/client/modules/auth/routes/onboarding.tsx | 83 +- .../modules/settings/routes/settings.tsx | 72 +- app/context.ts | 8 +- app/drizzle/0029_boring_luke_cage.sql | 65 + app/drizzle/0030_lower-trim-username.sql | 2 + app/drizzle/meta/0029_snapshot.json | 1113 +++ app/drizzle/meta/0030_snapshot.json | 1113 +++ app/drizzle/meta/_journal.json | 430 +- .../auth-middlewares/convert-legacy-user.ts | 50 + app/lib/auth-middlewares/only-one-user.ts | 17 + app/lib/auth.ts | 49 + app/middleware/auth.ts | 11 +- app/server/app.ts | 2 + app/server/cli/commands/reset-password.ts | 49 +- app/server/db/db.ts | 52 +- app/server/db/schema.ts | 157 +- app/server/index.ts | 5 +- app/server/jobs/cleanup-sessions.ts | 10 - app/server/modules/auth/auth.controller.ts | 157 +- app/server/modules/auth/auth.dto.ts | 129 - app/server/modules/auth/auth.middleware.ts | 52 +- app/server/modules/auth/auth.service.ts | 167 +- app/server/modules/auth/helpers.ts | 26 + .../__tests__/backups.controller.test.ts | 28 +- .../__tests__/events.controller.test.ts | 12 +- app/server/modules/lifecycle/startup.ts | 2 - .../notifications.controller.test.ts | 27 +- .../__tests__/repositories.controller.test.ts | 22 +- .../__tests__/system.controller.test.ts | 22 +- .../modules/system/system.controller.ts | 14 +- .../__tests__/volumes.controller.test.ts | 22 +- app/server/utils/crypto.ts | 12 + app/test/helpers/auth.ts | 41 +- app/test/setup.ts | 13 +- bun.lock | 117 + package.json | 225 +- 59 files changed, 10126 insertions(+), 6582 deletions(-) create mode 100644 app/client/lib/auth-client.ts create mode 100644 app/drizzle/0029_boring_luke_cage.sql create mode 100644 app/drizzle/0030_lower-trim-username.sql create mode 100644 app/drizzle/meta/0029_snapshot.json create mode 100644 app/drizzle/meta/0030_snapshot.json create mode 100644 app/lib/auth-middlewares/convert-legacy-user.ts create mode 100644 app/lib/auth-middlewares/only-one-user.ts create mode 100644 app/lib/auth.ts delete mode 100644 app/server/jobs/cleanup-sessions.ts create mode 100644 app/server/modules/auth/helpers.ts diff --git a/.dockerignore b/.dockerignore index d2d3fe67..1322a729 100644 --- a/.dockerignore +++ b/.dockerignore @@ -21,3 +21,4 @@ !NOTICES.md !LICENSES/** +node_modules/** diff --git a/.gitignore b/.gitignore index 99880c84..e12444f2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ CLAUDE.md mutagen.yml.lock notes.md smb-password.txt +cache.db diff --git a/app/client/api-client/@tanstack/react-query.gen.ts b/app/client/api-client/@tanstack/react-query.gen.ts index 108068f5..8f40258f 100644 --- a/app/client/api-client/@tanstack/react-query.gen.ts +++ b/app/client/api-client/@tanstack/react-query.gen.ts @@ -1,997 +1,1551 @@ // This file is auto-generated by @hey-api/openapi-ts -import { type DefaultError, queryOptions, type UseMutationOptions } from '@tanstack/react-query'; +import { + type DefaultError, + queryOptions, + type UseMutationOptions, +} from "@tanstack/react-query"; -import { client } from '../client.gen'; -import { browseFilesystem, changePassword, createBackupSchedule, createNotificationDestination, createRepository, createVolume, deleteBackupSchedule, deleteNotificationDestination, deleteRepository, deleteSnapshot, deleteSnapshots, deleteVolume, doctorRepository, downloadResticPassword, getBackupSchedule, getBackupScheduleForVolume, getMe, getMirrorCompatibility, getNotificationDestination, getRepository, getScheduleMirrors, getScheduleNotifications, getSnapshotDetails, getStatus, getSystemInfo, getUpdates, getVolume, healthCheckVolume, listBackupSchedules, listFiles, listNotificationDestinations, listRcloneRemotes, listRepositories, listSnapshotFiles, listSnapshots, listVolumes, login, logout, mountVolume, type Options, register, reorderBackupSchedules, restoreSnapshot, runBackupNow, runForget, stopBackup, tagSnapshots, testConnection, testNotificationDestination, unmountVolume, updateBackupSchedule, updateNotificationDestination, updateRepository, updateScheduleMirrors, updateScheduleNotifications, updateVolume } from '../sdk.gen'; -import type { BrowseFilesystemData, BrowseFilesystemResponse, ChangePasswordData, ChangePasswordResponse, CreateBackupScheduleData, CreateBackupScheduleResponse, CreateNotificationDestinationData, CreateNotificationDestinationResponse, CreateRepositoryData, CreateRepositoryResponse, CreateVolumeData, CreateVolumeResponse, DeleteBackupScheduleData, DeleteBackupScheduleResponse, DeleteNotificationDestinationData, DeleteNotificationDestinationResponse, DeleteRepositoryData, DeleteRepositoryResponse, DeleteSnapshotData, DeleteSnapshotResponse, DeleteSnapshotsData, DeleteSnapshotsResponse, DeleteVolumeData, DeleteVolumeResponse, DoctorRepositoryData, DoctorRepositoryResponse, DownloadResticPasswordData, DownloadResticPasswordResponse, GetBackupScheduleData, GetBackupScheduleForVolumeData, GetBackupScheduleForVolumeResponse, GetBackupScheduleResponse, GetMeData, GetMeResponse, GetMirrorCompatibilityData, GetMirrorCompatibilityResponse, GetNotificationDestinationData, GetNotificationDestinationResponse, GetRepositoryData, GetRepositoryResponse, GetScheduleMirrorsData, GetScheduleMirrorsResponse, GetScheduleNotificationsData, GetScheduleNotificationsResponse, GetSnapshotDetailsData, GetSnapshotDetailsResponse, GetStatusData, GetStatusResponse, GetSystemInfoData, GetSystemInfoResponse, GetUpdatesData, GetUpdatesResponse, GetVolumeData, GetVolumeResponse, HealthCheckVolumeData, HealthCheckVolumeResponse, ListBackupSchedulesData, ListBackupSchedulesResponse, ListFilesData, ListFilesResponse, ListNotificationDestinationsData, ListNotificationDestinationsResponse, ListRcloneRemotesData, ListRcloneRemotesResponse, ListRepositoriesData, ListRepositoriesResponse, ListSnapshotFilesData, ListSnapshotFilesResponse, ListSnapshotsData, ListSnapshotsResponse, ListVolumesData, ListVolumesResponse, LoginData, LoginResponse, LogoutData, LogoutResponse, MountVolumeData, MountVolumeResponse, RegisterData, RegisterResponse, ReorderBackupSchedulesData, ReorderBackupSchedulesResponse, RestoreSnapshotData, RestoreSnapshotResponse, RunBackupNowData, RunBackupNowResponse, RunForgetData, RunForgetResponse, StopBackupData, StopBackupResponse, TagSnapshotsData, TagSnapshotsResponse, TestConnectionData, TestConnectionResponse, TestNotificationDestinationData, TestNotificationDestinationResponse, UnmountVolumeData, UnmountVolumeResponse, UpdateBackupScheduleData, UpdateBackupScheduleResponse, UpdateNotificationDestinationData, UpdateNotificationDestinationResponse, UpdateRepositoryData, UpdateRepositoryResponse, UpdateScheduleMirrorsData, UpdateScheduleMirrorsResponse, UpdateScheduleNotificationsData, UpdateScheduleNotificationsResponse, UpdateVolumeData, UpdateVolumeResponse } from '../types.gen'; - -/** - * Register a new user - */ -export const registerMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await register({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; -}; - -/** - * Login with username and password - */ -export const loginMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await login({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; -}; - -/** - * Logout current user - */ -export const logoutMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await logout({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; -}; +import { client } from "../client.gen"; +import { + browseFilesystem, + createBackupSchedule, + createNotificationDestination, + createRepository, + createVolume, + deleteBackupSchedule, + deleteNotificationDestination, + deleteRepository, + deleteSnapshot, + deleteSnapshots, + deleteVolume, + doctorRepository, + downloadResticPassword, + getBackupSchedule, + getBackupScheduleForVolume, + getMirrorCompatibility, + getNotificationDestination, + getRepository, + getScheduleMirrors, + getScheduleNotifications, + getSnapshotDetails, + getStatus, + getSystemInfo, + getUpdates, + getVolume, + healthCheckVolume, + listBackupSchedules, + listFiles, + listNotificationDestinations, + listRcloneRemotes, + listRepositories, + listSnapshotFiles, + listSnapshots, + listVolumes, + mountVolume, + type Options, + reorderBackupSchedules, + restoreSnapshot, + runBackupNow, + runForget, + stopBackup, + tagSnapshots, + testConnection, + testNotificationDestination, + unmountVolume, + updateBackupSchedule, + updateNotificationDestination, + updateRepository, + updateScheduleMirrors, + updateScheduleNotifications, + updateVolume, +} from "../sdk.gen"; +import type { + BrowseFilesystemData, + BrowseFilesystemResponse, + CreateBackupScheduleData, + CreateBackupScheduleResponse, + CreateNotificationDestinationData, + CreateNotificationDestinationResponse, + CreateRepositoryData, + CreateRepositoryResponse, + CreateVolumeData, + CreateVolumeResponse, + DeleteBackupScheduleData, + DeleteBackupScheduleResponse, + DeleteNotificationDestinationData, + DeleteNotificationDestinationResponse, + DeleteRepositoryData, + DeleteRepositoryResponse, + DeleteSnapshotData, + DeleteSnapshotResponse, + DeleteSnapshotsData, + DeleteSnapshotsResponse, + DeleteVolumeData, + DeleteVolumeResponse, + DoctorRepositoryData, + DoctorRepositoryResponse, + DownloadResticPasswordData, + DownloadResticPasswordResponse, + GetBackupScheduleData, + GetBackupScheduleForVolumeData, + GetBackupScheduleForVolumeResponse, + GetBackupScheduleResponse, + GetMirrorCompatibilityData, + GetMirrorCompatibilityResponse, + GetNotificationDestinationData, + GetNotificationDestinationResponse, + GetRepositoryData, + GetRepositoryResponse, + GetScheduleMirrorsData, + GetScheduleMirrorsResponse, + GetScheduleNotificationsData, + GetScheduleNotificationsResponse, + GetSnapshotDetailsData, + GetSnapshotDetailsResponse, + GetStatusData, + GetStatusResponse, + GetSystemInfoData, + GetSystemInfoResponse, + GetUpdatesData, + GetUpdatesResponse, + GetVolumeData, + GetVolumeResponse, + HealthCheckVolumeData, + HealthCheckVolumeResponse, + ListBackupSchedulesData, + ListBackupSchedulesResponse, + ListFilesData, + ListFilesResponse, + ListNotificationDestinationsData, + ListNotificationDestinationsResponse, + ListRcloneRemotesData, + ListRcloneRemotesResponse, + ListRepositoriesData, + ListRepositoriesResponse, + ListSnapshotFilesData, + ListSnapshotFilesResponse, + ListSnapshotsData, + ListSnapshotsResponse, + ListVolumesData, + ListVolumesResponse, + MountVolumeData, + MountVolumeResponse, + ReorderBackupSchedulesData, + ReorderBackupSchedulesResponse, + RestoreSnapshotData, + RestoreSnapshotResponse, + RunBackupNowData, + RunBackupNowResponse, + RunForgetData, + RunForgetResponse, + StopBackupData, + StopBackupResponse, + TagSnapshotsData, + TagSnapshotsResponse, + TestConnectionData, + TestConnectionResponse, + TestNotificationDestinationData, + TestNotificationDestinationResponse, + UnmountVolumeData, + UnmountVolumeResponse, + UpdateBackupScheduleData, + UpdateBackupScheduleResponse, + UpdateNotificationDestinationData, + UpdateNotificationDestinationResponse, + UpdateRepositoryData, + UpdateRepositoryResponse, + UpdateScheduleMirrorsData, + UpdateScheduleMirrorsResponse, + UpdateScheduleNotificationsData, + UpdateScheduleNotificationsResponse, + UpdateVolumeData, + UpdateVolumeResponse, +} from "../types.gen"; export type QueryKey = [ - Pick & { - _id: string; - _infinite?: boolean; - tags?: ReadonlyArray; - } + Pick & { + _id: string; + _infinite?: boolean; + tags?: ReadonlyArray; + }, ]; -const createQueryKey = (id: string, options?: TOptions, infinite?: boolean, tags?: ReadonlyArray): [ - QueryKey[0] -] => { - const params: QueryKey[0] = { _id: id, baseUrl: options?.baseUrl || (options?.client ?? client).getConfig().baseUrl } as QueryKey[0]; - if (infinite) { - params._infinite = infinite; - } - if (tags) { - params.tags = tags; - } - if (options?.body) { - params.body = options.body; - } - if (options?.headers) { - params.headers = options.headers; - } - if (options?.path) { - params.path = options.path; - } - if (options?.query) { - params.query = options.query; - } - return [params]; +const createQueryKey = ( + id: string, + options?: TOptions, + infinite?: boolean, + tags?: ReadonlyArray, +): [QueryKey[0]] => { + const params: QueryKey[0] = { + _id: id, + baseUrl: + options?.baseUrl || (options?.client ?? client).getConfig().baseUrl, + } as QueryKey[0]; + if (infinite) { + params._infinite = infinite; + } + if (tags) { + params.tags = tags; + } + if (options?.body) { + params.body = options.body; + } + if (options?.headers) { + params.headers = options.headers; + } + if (options?.path) { + params.path = options.path; + } + if (options?.query) { + params.query = options.query; + } + return [params]; }; -export const getMeQueryKey = (options?: Options) => createQueryKey('getMe', options); - -/** - * Get current authenticated user - */ -export const getMeOptions = (options?: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getMe({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: getMeQueryKey(options) -}); - -export const getStatusQueryKey = (options?: Options) => createQueryKey('getStatus', options); +export const getStatusQueryKey = (options?: Options) => + createQueryKey("getStatus", options); /** * Get authentication system status */ -export const getStatusOptions = (options?: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getStatus({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: getStatusQueryKey(options) -}); +export const getStatusOptions = (options?: Options) => + queryOptions< + GetStatusResponse, + DefaultError, + GetStatusResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getStatus({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getStatusQueryKey(options), + }); -/** - * Change current user password - */ -export const changePasswordMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await changePassword({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; -}; - -export const listVolumesQueryKey = (options?: Options) => createQueryKey('listVolumes', options); +export const listVolumesQueryKey = (options?: Options) => + createQueryKey("listVolumes", options); /** * List all volumes */ -export const listVolumesOptions = (options?: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await listVolumes({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: listVolumesQueryKey(options) -}); +export const listVolumesOptions = (options?: Options) => + queryOptions< + ListVolumesResponse, + DefaultError, + ListVolumesResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await listVolumes({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: listVolumesQueryKey(options), + }); /** * Create a new volume */ -export const createVolumeMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await createVolume({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const createVolumeMutation = ( + options?: Partial>, +): UseMutationOptions< + CreateVolumeResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + CreateVolumeResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await createVolume({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; /** * Test connection to backend */ -export const testConnectionMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await testConnection({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const testConnectionMutation = ( + options?: Partial>, +): UseMutationOptions< + TestConnectionResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + TestConnectionResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await testConnection({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; /** * Delete a volume */ -export const deleteVolumeMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await deleteVolume({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const deleteVolumeMutation = ( + options?: Partial>, +): UseMutationOptions< + DeleteVolumeResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + DeleteVolumeResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await deleteVolume({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; -export const getVolumeQueryKey = (options: Options) => createQueryKey('getVolume', options); +export const getVolumeQueryKey = (options: Options) => + createQueryKey("getVolume", options); /** * Get a volume by name */ -export const getVolumeOptions = (options: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getVolume({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: getVolumeQueryKey(options) -}); +export const getVolumeOptions = (options: Options) => + queryOptions< + GetVolumeResponse, + DefaultError, + GetVolumeResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getVolume({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getVolumeQueryKey(options), + }); /** * Update a volume's configuration */ -export const updateVolumeMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await updateVolume({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const updateVolumeMutation = ( + options?: Partial>, +): UseMutationOptions< + UpdateVolumeResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + UpdateVolumeResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await updateVolume({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; /** * Mount a volume */ -export const mountVolumeMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await mountVolume({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const mountVolumeMutation = ( + options?: Partial>, +): UseMutationOptions< + MountVolumeResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + MountVolumeResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await mountVolume({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; /** * Unmount a volume */ -export const unmountVolumeMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await unmountVolume({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const unmountVolumeMutation = ( + options?: Partial>, +): UseMutationOptions< + UnmountVolumeResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + UnmountVolumeResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await unmountVolume({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; /** * Perform a health check on a volume */ -export const healthCheckVolumeMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await healthCheckVolume({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const healthCheckVolumeMutation = ( + options?: Partial>, +): UseMutationOptions< + HealthCheckVolumeResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + HealthCheckVolumeResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await healthCheckVolume({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; -export const listFilesQueryKey = (options: Options) => createQueryKey('listFiles', options); +export const listFilesQueryKey = (options: Options) => + createQueryKey("listFiles", options); /** * List files in a volume directory */ -export const listFilesOptions = (options: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await listFiles({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: listFilesQueryKey(options) -}); +export const listFilesOptions = (options: Options) => + queryOptions< + ListFilesResponse, + DefaultError, + ListFilesResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await listFiles({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: listFilesQueryKey(options), + }); -export const browseFilesystemQueryKey = (options?: Options) => createQueryKey('browseFilesystem', options); +export const browseFilesystemQueryKey = ( + options?: Options, +) => createQueryKey("browseFilesystem", options); /** * Browse directories on the host filesystem */ -export const browseFilesystemOptions = (options?: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await browseFilesystem({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: browseFilesystemQueryKey(options) -}); +export const browseFilesystemOptions = ( + options?: Options, +) => + queryOptions< + BrowseFilesystemResponse, + DefaultError, + BrowseFilesystemResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await browseFilesystem({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: browseFilesystemQueryKey(options), + }); -export const listRepositoriesQueryKey = (options?: Options) => createQueryKey('listRepositories', options); +export const listRepositoriesQueryKey = ( + options?: Options, +) => createQueryKey("listRepositories", options); /** * List all repositories */ -export const listRepositoriesOptions = (options?: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await listRepositories({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: listRepositoriesQueryKey(options) -}); +export const listRepositoriesOptions = ( + options?: Options, +) => + queryOptions< + ListRepositoriesResponse, + DefaultError, + ListRepositoriesResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await listRepositories({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: listRepositoriesQueryKey(options), + }); /** * Create a new restic repository */ -export const createRepositoryMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await createRepository({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const createRepositoryMutation = ( + options?: Partial>, +): UseMutationOptions< + CreateRepositoryResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + CreateRepositoryResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await createRepository({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; -export const listRcloneRemotesQueryKey = (options?: Options) => createQueryKey('listRcloneRemotes', options); +export const listRcloneRemotesQueryKey = ( + options?: Options, +) => createQueryKey("listRcloneRemotes", options); /** * List all configured rclone remotes on the host system */ -export const listRcloneRemotesOptions = (options?: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await listRcloneRemotes({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: listRcloneRemotesQueryKey(options) -}); +export const listRcloneRemotesOptions = ( + options?: Options, +) => + queryOptions< + ListRcloneRemotesResponse, + DefaultError, + ListRcloneRemotesResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await listRcloneRemotes({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: listRcloneRemotesQueryKey(options), + }); /** * Delete a repository */ -export const deleteRepositoryMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await deleteRepository({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const deleteRepositoryMutation = ( + options?: Partial>, +): UseMutationOptions< + DeleteRepositoryResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + DeleteRepositoryResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await deleteRepository({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; -export const getRepositoryQueryKey = (options: Options) => createQueryKey('getRepository', options); +export const getRepositoryQueryKey = (options: Options) => + createQueryKey("getRepository", options); /** * Get a single repository by ID */ -export const getRepositoryOptions = (options: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getRepository({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: getRepositoryQueryKey(options) -}); +export const getRepositoryOptions = (options: Options) => + queryOptions< + GetRepositoryResponse, + DefaultError, + GetRepositoryResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getRepository({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getRepositoryQueryKey(options), + }); /** * Update a repository's name or settings */ -export const updateRepositoryMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await updateRepository({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const updateRepositoryMutation = ( + options?: Partial>, +): UseMutationOptions< + UpdateRepositoryResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + UpdateRepositoryResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await updateRepository({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; /** * Delete multiple snapshots from a repository */ -export const deleteSnapshotsMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await deleteSnapshots({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const deleteSnapshotsMutation = ( + options?: Partial>, +): UseMutationOptions< + DeleteSnapshotsResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + DeleteSnapshotsResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await deleteSnapshots({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; -export const listSnapshotsQueryKey = (options: Options) => createQueryKey('listSnapshots', options); +export const listSnapshotsQueryKey = (options: Options) => + createQueryKey("listSnapshots", options); /** * List all snapshots in a repository */ -export const listSnapshotsOptions = (options: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await listSnapshots({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: listSnapshotsQueryKey(options) -}); +export const listSnapshotsOptions = (options: Options) => + queryOptions< + ListSnapshotsResponse, + DefaultError, + ListSnapshotsResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await listSnapshots({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: listSnapshotsQueryKey(options), + }); /** * Delete a specific snapshot from a repository */ -export const deleteSnapshotMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await deleteSnapshot({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const deleteSnapshotMutation = ( + options?: Partial>, +): UseMutationOptions< + DeleteSnapshotResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + DeleteSnapshotResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await deleteSnapshot({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; -export const getSnapshotDetailsQueryKey = (options: Options) => createQueryKey('getSnapshotDetails', options); +export const getSnapshotDetailsQueryKey = ( + options: Options, +) => createQueryKey("getSnapshotDetails", options); /** * Get details of a specific snapshot */ -export const getSnapshotDetailsOptions = (options: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getSnapshotDetails({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: getSnapshotDetailsQueryKey(options) -}); +export const getSnapshotDetailsOptions = ( + options: Options, +) => + queryOptions< + GetSnapshotDetailsResponse, + DefaultError, + GetSnapshotDetailsResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getSnapshotDetails({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getSnapshotDetailsQueryKey(options), + }); -export const listSnapshotFilesQueryKey = (options: Options) => createQueryKey('listSnapshotFiles', options); +export const listSnapshotFilesQueryKey = ( + options: Options, +) => createQueryKey("listSnapshotFiles", options); /** * List files and directories in a snapshot */ -export const listSnapshotFilesOptions = (options: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await listSnapshotFiles({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: listSnapshotFilesQueryKey(options) -}); +export const listSnapshotFilesOptions = ( + options: Options, +) => + queryOptions< + ListSnapshotFilesResponse, + DefaultError, + ListSnapshotFilesResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await listSnapshotFiles({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: listSnapshotFilesQueryKey(options), + }); /** * Restore a snapshot to a target path on the filesystem */ -export const restoreSnapshotMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await restoreSnapshot({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const restoreSnapshotMutation = ( + options?: Partial>, +): UseMutationOptions< + RestoreSnapshotResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + RestoreSnapshotResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await restoreSnapshot({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; /** * Run doctor operations on a repository to fix common issues (unlock, check, repair index). Use this when the repository is locked or has errors. */ -export const doctorRepositoryMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await doctorRepository({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const doctorRepositoryMutation = ( + options?: Partial>, +): UseMutationOptions< + DoctorRepositoryResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + DoctorRepositoryResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await doctorRepository({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; /** * Tag multiple snapshots in a repository */ -export const tagSnapshotsMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await tagSnapshots({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const tagSnapshotsMutation = ( + options?: Partial>, +): UseMutationOptions< + TagSnapshotsResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + TagSnapshotsResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await tagSnapshots({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; -export const listBackupSchedulesQueryKey = (options?: Options) => createQueryKey('listBackupSchedules', options); +export const listBackupSchedulesQueryKey = ( + options?: Options, +) => createQueryKey("listBackupSchedules", options); /** * List all backup schedules */ -export const listBackupSchedulesOptions = (options?: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await listBackupSchedules({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: listBackupSchedulesQueryKey(options) -}); +export const listBackupSchedulesOptions = ( + options?: Options, +) => + queryOptions< + ListBackupSchedulesResponse, + DefaultError, + ListBackupSchedulesResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await listBackupSchedules({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: listBackupSchedulesQueryKey(options), + }); /** * Create a new backup schedule for a volume */ -export const createBackupScheduleMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await createBackupSchedule({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const createBackupScheduleMutation = ( + options?: Partial>, +): UseMutationOptions< + CreateBackupScheduleResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + CreateBackupScheduleResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await createBackupSchedule({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; /** * Delete a backup schedule */ -export const deleteBackupScheduleMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await deleteBackupSchedule({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const deleteBackupScheduleMutation = ( + options?: Partial>, +): UseMutationOptions< + DeleteBackupScheduleResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + DeleteBackupScheduleResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await deleteBackupSchedule({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; -export const getBackupScheduleQueryKey = (options: Options) => createQueryKey('getBackupSchedule', options); +export const getBackupScheduleQueryKey = ( + options: Options, +) => createQueryKey("getBackupSchedule", options); /** * Get a backup schedule by ID */ -export const getBackupScheduleOptions = (options: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getBackupSchedule({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: getBackupScheduleQueryKey(options) -}); +export const getBackupScheduleOptions = ( + options: Options, +) => + queryOptions< + GetBackupScheduleResponse, + DefaultError, + GetBackupScheduleResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getBackupSchedule({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getBackupScheduleQueryKey(options), + }); /** * Update a backup schedule */ -export const updateBackupScheduleMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await updateBackupSchedule({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const updateBackupScheduleMutation = ( + options?: Partial>, +): UseMutationOptions< + UpdateBackupScheduleResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + UpdateBackupScheduleResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await updateBackupSchedule({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; -export const getBackupScheduleForVolumeQueryKey = (options: Options) => createQueryKey('getBackupScheduleForVolume', options); +export const getBackupScheduleForVolumeQueryKey = ( + options: Options, +) => createQueryKey("getBackupScheduleForVolume", options); /** * Get a backup schedule for a specific volume */ -export const getBackupScheduleForVolumeOptions = (options: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getBackupScheduleForVolume({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: getBackupScheduleForVolumeQueryKey(options) -}); +export const getBackupScheduleForVolumeOptions = ( + options: Options, +) => + queryOptions< + GetBackupScheduleForVolumeResponse, + DefaultError, + GetBackupScheduleForVolumeResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getBackupScheduleForVolume({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getBackupScheduleForVolumeQueryKey(options), + }); /** * Trigger a backup immediately for a schedule */ -export const runBackupNowMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await runBackupNow({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const runBackupNowMutation = ( + options?: Partial>, +): UseMutationOptions< + RunBackupNowResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + RunBackupNowResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await runBackupNow({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; /** * Stop a backup that is currently in progress */ -export const stopBackupMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await stopBackup({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const stopBackupMutation = ( + options?: Partial>, +): UseMutationOptions< + StopBackupResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + StopBackupResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await stopBackup({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; /** * Manually apply retention policy to clean up old snapshots */ -export const runForgetMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await runForget({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const runForgetMutation = ( + options?: Partial>, +): UseMutationOptions< + RunForgetResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + RunForgetResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await runForget({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; -export const getScheduleNotificationsQueryKey = (options: Options) => createQueryKey('getScheduleNotifications', options); +export const getScheduleNotificationsQueryKey = ( + options: Options, +) => createQueryKey("getScheduleNotifications", options); /** * Get notification assignments for a backup schedule */ -export const getScheduleNotificationsOptions = (options: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getScheduleNotifications({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: getScheduleNotificationsQueryKey(options) -}); +export const getScheduleNotificationsOptions = ( + options: Options, +) => + queryOptions< + GetScheduleNotificationsResponse, + DefaultError, + GetScheduleNotificationsResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getScheduleNotifications({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getScheduleNotificationsQueryKey(options), + }); /** * Update notification assignments for a backup schedule */ -export const updateScheduleNotificationsMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await updateScheduleNotifications({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const updateScheduleNotificationsMutation = ( + options?: Partial>, +): UseMutationOptions< + UpdateScheduleNotificationsResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + UpdateScheduleNotificationsResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await updateScheduleNotifications({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; -export const getScheduleMirrorsQueryKey = (options: Options) => createQueryKey('getScheduleMirrors', options); +export const getScheduleMirrorsQueryKey = ( + options: Options, +) => createQueryKey("getScheduleMirrors", options); /** * Get mirror repository assignments for a backup schedule */ -export const getScheduleMirrorsOptions = (options: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getScheduleMirrors({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: getScheduleMirrorsQueryKey(options) -}); +export const getScheduleMirrorsOptions = ( + options: Options, +) => + queryOptions< + GetScheduleMirrorsResponse, + DefaultError, + GetScheduleMirrorsResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getScheduleMirrors({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getScheduleMirrorsQueryKey(options), + }); /** * Update mirror repository assignments for a backup schedule */ -export const updateScheduleMirrorsMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await updateScheduleMirrors({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const updateScheduleMirrorsMutation = ( + options?: Partial>, +): UseMutationOptions< + UpdateScheduleMirrorsResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + UpdateScheduleMirrorsResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await updateScheduleMirrors({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; -export const getMirrorCompatibilityQueryKey = (options: Options) => createQueryKey('getMirrorCompatibility', options); +export const getMirrorCompatibilityQueryKey = ( + options: Options, +) => createQueryKey("getMirrorCompatibility", options); /** * Get mirror compatibility info for all repositories relative to a backup schedule's primary repository */ -export const getMirrorCompatibilityOptions = (options: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getMirrorCompatibility({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: getMirrorCompatibilityQueryKey(options) -}); +export const getMirrorCompatibilityOptions = ( + options: Options, +) => + queryOptions< + GetMirrorCompatibilityResponse, + DefaultError, + GetMirrorCompatibilityResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getMirrorCompatibility({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getMirrorCompatibilityQueryKey(options), + }); /** * Reorder backup schedules by providing an array of schedule IDs in the desired order */ -export const reorderBackupSchedulesMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await reorderBackupSchedules({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const reorderBackupSchedulesMutation = ( + options?: Partial>, +): UseMutationOptions< + ReorderBackupSchedulesResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + ReorderBackupSchedulesResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await reorderBackupSchedules({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; -export const listNotificationDestinationsQueryKey = (options?: Options) => createQueryKey('listNotificationDestinations', options); +export const listNotificationDestinationsQueryKey = ( + options?: Options, +) => createQueryKey("listNotificationDestinations", options); /** * List all notification destinations */ -export const listNotificationDestinationsOptions = (options?: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await listNotificationDestinations({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: listNotificationDestinationsQueryKey(options) -}); +export const listNotificationDestinationsOptions = ( + options?: Options, +) => + queryOptions< + ListNotificationDestinationsResponse, + DefaultError, + ListNotificationDestinationsResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await listNotificationDestinations({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: listNotificationDestinationsQueryKey(options), + }); /** * Create a new notification destination */ -export const createNotificationDestinationMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await createNotificationDestination({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const createNotificationDestinationMutation = ( + options?: Partial>, +): UseMutationOptions< + CreateNotificationDestinationResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + CreateNotificationDestinationResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await createNotificationDestination({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; /** * Delete a notification destination */ -export const deleteNotificationDestinationMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await deleteNotificationDestination({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const deleteNotificationDestinationMutation = ( + options?: Partial>, +): UseMutationOptions< + DeleteNotificationDestinationResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + DeleteNotificationDestinationResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await deleteNotificationDestination({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; -export const getNotificationDestinationQueryKey = (options: Options) => createQueryKey('getNotificationDestination', options); +export const getNotificationDestinationQueryKey = ( + options: Options, +) => createQueryKey("getNotificationDestination", options); /** * Get a notification destination by ID */ -export const getNotificationDestinationOptions = (options: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getNotificationDestination({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: getNotificationDestinationQueryKey(options) -}); +export const getNotificationDestinationOptions = ( + options: Options, +) => + queryOptions< + GetNotificationDestinationResponse, + DefaultError, + GetNotificationDestinationResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getNotificationDestination({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getNotificationDestinationQueryKey(options), + }); /** * Update a notification destination */ -export const updateNotificationDestinationMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await updateNotificationDestination({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const updateNotificationDestinationMutation = ( + options?: Partial>, +): UseMutationOptions< + UpdateNotificationDestinationResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + UpdateNotificationDestinationResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await updateNotificationDestination({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; /** * Test a notification destination by sending a test message */ -export const testNotificationDestinationMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await testNotificationDestination({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const testNotificationDestinationMutation = ( + options?: Partial>, +): UseMutationOptions< + TestNotificationDestinationResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + TestNotificationDestinationResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await testNotificationDestination({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; -export const getSystemInfoQueryKey = (options?: Options) => createQueryKey('getSystemInfo', options); +export const getSystemInfoQueryKey = (options?: Options) => + createQueryKey("getSystemInfo", options); /** * Get system information including available capabilities */ -export const getSystemInfoOptions = (options?: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getSystemInfo({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: getSystemInfoQueryKey(options) -}); +export const getSystemInfoOptions = (options?: Options) => + queryOptions< + GetSystemInfoResponse, + DefaultError, + GetSystemInfoResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getSystemInfo({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getSystemInfoQueryKey(options), + }); -export const getUpdatesQueryKey = (options?: Options) => createQueryKey('getUpdates', options); +export const getUpdatesQueryKey = (options?: Options) => + createQueryKey("getUpdates", options); /** * Check for application updates from GitHub */ -export const getUpdatesOptions = (options?: Options) => queryOptions>({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getUpdates({ - ...options, - ...queryKey[0], - signal, - throwOnError: true - }); - return data; - }, - queryKey: getUpdatesQueryKey(options) -}); +export const getUpdatesOptions = (options?: Options) => + queryOptions< + GetUpdatesResponse, + DefaultError, + GetUpdatesResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getUpdates({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getUpdatesQueryKey(options), + }); /** * Download the Restic password file for backup recovery. Requires password re-authentication. */ -export const downloadResticPasswordMutation = (options?: Partial>): UseMutationOptions> => { - const mutationOptions: UseMutationOptions> = { - mutationFn: async (fnOptions) => { - const { data } = await downloadResticPassword({ - ...options, - ...fnOptions, - throwOnError: true - }); - return data; - } - }; - return mutationOptions; +export const downloadResticPasswordMutation = ( + options?: Partial>, +): UseMutationOptions< + DownloadResticPasswordResponse, + DefaultError, + Options +> => { + const mutationOptions: UseMutationOptions< + DownloadResticPasswordResponse, + DefaultError, + Options + > = { + mutationFn: async (fnOptions) => { + const { data } = await downloadResticPassword({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; }; diff --git a/app/client/api-client/client.gen.ts b/app/client/api-client/client.gen.ts index a31c7b49..92f3451a 100644 --- a/app/client/api-client/client.gen.ts +++ b/app/client/api-client/client.gen.ts @@ -1,7 +1,12 @@ // This file is auto-generated by @hey-api/openapi-ts -import { type ClientOptions, type Config, createClient, createConfig } from './client'; -import type { ClientOptions as ClientOptions2 } from './types.gen'; +import { + type ClientOptions, + type Config, + createClient, + createConfig, +} from "./client"; +import type { ClientOptions as ClientOptions2 } from "./types.gen"; /** * The `createClientConfig()` function will be called on client initialization @@ -11,6 +16,10 @@ import type { ClientOptions as ClientOptions2 } from './types.gen'; * `setConfig()`. This is useful for example if you're using Next.js * to ensure your client always has the correct values. */ -export type CreateClientConfig = (override?: Config) => Config & T>; +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; -export const client = createClient(createConfig({ baseUrl: 'http://localhost:4096' })); +export const client = createClient( + createConfig({ baseUrl: "http://localhost:4096" }), +); diff --git a/app/client/api-client/client/client.gen.ts b/app/client/api-client/client/client.gen.ts index c2a5190c..a8a478dc 100644 --- a/app/client/api-client/client/client.gen.ts +++ b/app/client/api-client/client/client.gen.ts @@ -1,301 +1,301 @@ // This file is auto-generated by @hey-api/openapi-ts -import { createSseClient } from '../core/serverSentEvents.gen'; -import type { HttpMethod } from '../core/types.gen'; -import { getValidRequestBody } from '../core/utils.gen'; +import { createSseClient } from "../core/serverSentEvents.gen"; +import type { HttpMethod } from "../core/types.gen"; +import { getValidRequestBody } from "../core/utils.gen"; import type { - Client, - Config, - RequestOptions, - ResolvedRequestOptions, -} from './types.gen'; + Client, + Config, + RequestOptions, + ResolvedRequestOptions, +} from "./types.gen"; import { - buildUrl, - createConfig, - createInterceptors, - getParseAs, - mergeConfigs, - mergeHeaders, - setAuthParams, -} from './utils.gen'; + buildUrl, + createConfig, + createInterceptors, + getParseAs, + mergeConfigs, + mergeHeaders, + setAuthParams, +} from "./utils.gen"; -type ReqInit = Omit & { - body?: any; - headers: ReturnType; +type ReqInit = Omit & { + body?: any; + headers: ReturnType; }; export const createClient = (config: Config = {}): Client => { - let _config = mergeConfigs(createConfig(), config); + let _config = mergeConfigs(createConfig(), config); - const getConfig = (): Config => ({ ..._config }); + const getConfig = (): Config => ({ ..._config }); - const setConfig = (config: Config): Config => { - _config = mergeConfigs(_config, config); - return getConfig(); - }; + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; - const interceptors = createInterceptors< - Request, - Response, - unknown, - ResolvedRequestOptions - >(); + const interceptors = createInterceptors< + Request, + Response, + unknown, + ResolvedRequestOptions + >(); - const beforeRequest = async (options: RequestOptions) => { - const opts = { - ..._config, - ...options, - fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, - headers: mergeHeaders(_config.headers, options.headers), - serializedBody: undefined, - }; + const beforeRequest = async (options: RequestOptions) => { + const opts = { + ..._config, + ...options, + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, + }; - if (opts.security) { - await setAuthParams({ - ...opts, - security: opts.security, - }); - } + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } - if (opts.requestValidator) { - await opts.requestValidator(opts); - } + if (opts.requestValidator) { + await opts.requestValidator(opts); + } - if (opts.body !== undefined && opts.bodySerializer) { - opts.serializedBody = opts.bodySerializer(opts.body); - } + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body); + } - // remove Content-Type header if body is empty to avoid sending invalid requests - if (opts.body === undefined || opts.serializedBody === '') { - opts.headers.delete('Content-Type'); - } + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === "") { + opts.headers.delete("Content-Type"); + } - const url = buildUrl(opts); + const url = buildUrl(opts); - return { opts, url }; - }; + return { opts, url }; + }; - const request: Client['request'] = async (options) => { - // @ts-expect-error - const { opts, url } = await beforeRequest(options); - const requestInit: ReqInit = { - redirect: 'follow', - ...opts, - body: getValidRequestBody(opts), - }; + const request: Client["request"] = async (options) => { + // @ts-expect-error + const { opts, url } = await beforeRequest(options); + const requestInit: ReqInit = { + redirect: "follow", + ...opts, + body: getValidRequestBody(opts), + }; - let request = new Request(url, requestInit); + let request = new Request(url, requestInit); - for (const fn of interceptors.request.fns) { - if (fn) { - request = await fn(request, opts); - } - } + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } - // fetch must be assigned here, otherwise it would throw the error: - // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation - const _fetch = opts.fetch!; - let response: Response; + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = opts.fetch!; + let response: Response; - try { - response = await _fetch(request); - } catch (error) { - // Handle fetch exceptions (AbortError, network errors, etc.) - let finalError = error; + try { + response = await _fetch(request); + } catch (error) { + // Handle fetch exceptions (AbortError, network errors, etc.) + let finalError = error; - for (const fn of interceptors.error.fns) { - if (fn) { - finalError = (await fn( - error, - undefined as any, - request, - opts, - )) as unknown; - } - } + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = (await fn( + error, + undefined as any, + request, + opts, + )) as unknown; + } + } - finalError = finalError || ({} as unknown); + finalError = finalError || ({} as unknown); - if (opts.throwOnError) { - throw finalError; - } + if (opts.throwOnError) { + throw finalError; + } - // Return error response - return opts.responseStyle === 'data' - ? undefined - : { - error: finalError, - request, - response: undefined as any, - }; - } + // Return error response + return opts.responseStyle === "data" + ? undefined + : { + error: finalError, + request, + response: undefined as any, + }; + } - for (const fn of interceptors.response.fns) { - if (fn) { - response = await fn(response, request, opts); - } - } + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } - const result = { - request, - response, - }; + const result = { + request, + response, + }; - if (response.ok) { - const parseAs = - (opts.parseAs === 'auto' - ? getParseAs(response.headers.get('Content-Type')) - : opts.parseAs) ?? 'json'; + if (response.ok) { + const parseAs = + (opts.parseAs === "auto" + ? getParseAs(response.headers.get("Content-Type")) + : opts.parseAs) ?? "json"; - if ( - response.status === 204 || - response.headers.get('Content-Length') === '0' - ) { - let emptyData: any; - switch (parseAs) { - case 'arrayBuffer': - case 'blob': - case 'text': - emptyData = await response[parseAs](); - break; - case 'formData': - emptyData = new FormData(); - break; - case 'stream': - emptyData = response.body; - break; - case 'json': - default: - emptyData = {}; - break; - } - return opts.responseStyle === 'data' - ? emptyData - : { - data: emptyData, - ...result, - }; - } + if ( + response.status === 204 || + response.headers.get("Content-Length") === "0" + ) { + let emptyData: any; + switch (parseAs) { + case "arrayBuffer": + case "blob": + case "text": + emptyData = await response[parseAs](); + break; + case "formData": + emptyData = new FormData(); + break; + case "stream": + emptyData = response.body; + break; + case "json": + default: + emptyData = {}; + break; + } + return opts.responseStyle === "data" + ? emptyData + : { + data: emptyData, + ...result, + }; + } - let data: any; - switch (parseAs) { - case 'arrayBuffer': - case 'blob': - case 'formData': - case 'json': - case 'text': - data = await response[parseAs](); - break; - case 'stream': - return opts.responseStyle === 'data' - ? response.body - : { - data: response.body, - ...result, - }; - } + let data: any; + switch (parseAs) { + case "arrayBuffer": + case "blob": + case "formData": + case "json": + case "text": + data = await response[parseAs](); + break; + case "stream": + return opts.responseStyle === "data" + ? response.body + : { + data: response.body, + ...result, + }; + } - if (parseAs === 'json') { - if (opts.responseValidator) { - await opts.responseValidator(data); - } + if (parseAs === "json") { + if (opts.responseValidator) { + await opts.responseValidator(data); + } - if (opts.responseTransformer) { - data = await opts.responseTransformer(data); - } - } + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } - return opts.responseStyle === 'data' - ? data - : { - data, - ...result, - }; - } + return opts.responseStyle === "data" + ? data + : { + data, + ...result, + }; + } - const textError = await response.text(); - let jsonError: unknown; + const textError = await response.text(); + let jsonError: unknown; - try { - jsonError = JSON.parse(textError); - } catch { - // noop - } + try { + jsonError = JSON.parse(textError); + } catch { + // noop + } - const error = jsonError ?? textError; - let finalError = error; + const error = jsonError ?? textError; + let finalError = error; - for (const fn of interceptors.error.fns) { - if (fn) { - finalError = (await fn(error, response, request, opts)) as string; - } - } + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = (await fn(error, response, request, opts)) as string; + } + } - finalError = finalError || ({} as string); + finalError = finalError || ({} as string); - if (opts.throwOnError) { - throw finalError; - } + if (opts.throwOnError) { + throw finalError; + } - // TODO: we probably want to return error and improve types - return opts.responseStyle === 'data' - ? undefined - : { - error: finalError, - ...result, - }; - }; + // TODO: we probably want to return error and improve types + return opts.responseStyle === "data" + ? undefined + : { + error: finalError, + ...result, + }; + }; - const makeMethodFn = - (method: Uppercase) => (options: RequestOptions) => - request({ ...options, method }); + const makeMethodFn = + (method: Uppercase) => (options: RequestOptions) => + request({ ...options, method }); - const makeSseFn = - (method: Uppercase) => async (options: RequestOptions) => { - const { opts, url } = await beforeRequest(options); - return createSseClient({ - ...opts, - body: opts.body as BodyInit | null | undefined, - headers: opts.headers as unknown as Record, - method, - onRequest: async (url, init) => { - let request = new Request(url, init); - for (const fn of interceptors.request.fns) { - if (fn) { - request = await fn(request, opts); - } - } - return request; - }, - url, - }); - }; + const makeSseFn = + (method: Uppercase) => async (options: RequestOptions) => { + const { opts, url } = await beforeRequest(options); + return createSseClient({ + ...opts, + body: opts.body as BodyInit | null | undefined, + headers: opts.headers as unknown as Record, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + return request; + }, + url, + }); + }; - return { - buildUrl, - connect: makeMethodFn('CONNECT'), - delete: makeMethodFn('DELETE'), - get: makeMethodFn('GET'), - getConfig, - head: makeMethodFn('HEAD'), - interceptors, - options: makeMethodFn('OPTIONS'), - patch: makeMethodFn('PATCH'), - post: makeMethodFn('POST'), - put: makeMethodFn('PUT'), - request, - setConfig, - sse: { - connect: makeSseFn('CONNECT'), - delete: makeSseFn('DELETE'), - get: makeSseFn('GET'), - head: makeSseFn('HEAD'), - options: makeSseFn('OPTIONS'), - patch: makeSseFn('PATCH'), - post: makeSseFn('POST'), - put: makeSseFn('PUT'), - trace: makeSseFn('TRACE'), - }, - trace: makeMethodFn('TRACE'), - } as Client; + return { + buildUrl, + connect: makeMethodFn("CONNECT"), + delete: makeMethodFn("DELETE"), + get: makeMethodFn("GET"), + getConfig, + head: makeMethodFn("HEAD"), + interceptors, + options: makeMethodFn("OPTIONS"), + patch: makeMethodFn("PATCH"), + post: makeMethodFn("POST"), + put: makeMethodFn("PUT"), + request, + setConfig, + sse: { + connect: makeSseFn("CONNECT"), + delete: makeSseFn("DELETE"), + get: makeSseFn("GET"), + head: makeSseFn("HEAD"), + options: makeSseFn("OPTIONS"), + patch: makeSseFn("PATCH"), + post: makeSseFn("POST"), + put: makeSseFn("PUT"), + trace: makeSseFn("TRACE"), + }, + trace: makeMethodFn("TRACE"), + } as Client; }; diff --git a/app/client/api-client/client/index.ts b/app/client/api-client/client/index.ts index b295edec..99ab72d0 100644 --- a/app/client/api-client/client/index.ts +++ b/app/client/api-client/client/index.ts @@ -1,25 +1,25 @@ // This file is auto-generated by @hey-api/openapi-ts -export type { Auth } from '../core/auth.gen'; -export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export type { Auth } from "../core/auth.gen"; +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 { createClient } from './client.gen'; + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from "../core/bodySerializer.gen"; +export { buildClientParams } from "../core/params.gen"; +export { serializeQueryKeyValue } from "../core/queryKeySerializer.gen"; +export { createClient } from "./client.gen"; export type { - Client, - ClientOptions, - Config, - CreateClientConfig, - Options, - RequestOptions, - RequestResult, - ResolvedRequestOptions, - ResponseStyle, - TDataShape, -} from './types.gen'; -export { createConfig, mergeHeaders } from './utils.gen'; + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from "./types.gen"; +export { createConfig, mergeHeaders } from "./utils.gen"; diff --git a/app/client/api-client/client/types.gen.ts b/app/client/api-client/client/types.gen.ts index b4a499cc..68310f36 100644 --- a/app/client/api-client/client/types.gen.ts +++ b/app/client/api-client/client/types.gen.ts @@ -1,210 +1,210 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Auth } from '../core/auth.gen'; +import type { Auth } from "../core/auth.gen"; import type { - ServerSentEventsOptions, - ServerSentEventsResult, -} from '../core/serverSentEvents.gen'; + ServerSentEventsOptions, + ServerSentEventsResult, +} from "../core/serverSentEvents.gen"; import type { - Client as CoreClient, - Config as CoreConfig, -} from '../core/types.gen'; -import type { Middleware } from './utils.gen'; + Client as CoreClient, + Config as CoreConfig, +} from "../core/types.gen"; +import type { Middleware } from "./utils.gen"; -export type ResponseStyle = 'data' | 'fields'; +export type ResponseStyle = "data" | "fields"; export interface Config - extends Omit, - CoreConfig { - /** - * Base URL for all requests made by this client. - */ - baseUrl?: T['baseUrl']; - /** - * Fetch API implementation. You can use this option to provide a custom - * fetch instance. - * - * @default globalThis.fetch - */ - fetch?: typeof fetch; - /** - * Please don't use the Fetch client for Next.js applications. The `next` - * options won't have any effect. - * - * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. - */ - next?: never; - /** - * Return the response data parsed in a specified format. By default, `auto` - * will infer the appropriate method from the `Content-Type` response header. - * You can override this behavior with any of the {@link Body} methods. - * Select `stream` if you don't want to parse response data at all. - * - * @default 'auto' - */ - parseAs?: - | 'arrayBuffer' - | 'auto' - | 'blob' - | 'formData' - | 'json' - | 'stream' - | 'text'; - /** - * Should we return only data or multiple fields (data, error, response, etc.)? - * - * @default 'fields' - */ - responseStyle?: ResponseStyle; - /** - * Throw an error instead of returning it in the response? - * - * @default false - */ - throwOnError?: T['throwOnError']; + extends Omit, + CoreConfig { + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T["baseUrl"]; + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | "arrayBuffer" + | "auto" + | "blob" + | "formData" + | "json" + | "stream" + | "text"; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T["throwOnError"]; } export interface RequestOptions< - TData = unknown, - TResponseStyle extends ResponseStyle = 'fields', - ThrowOnError extends boolean = boolean, - Url extends string = string, + TData = unknown, + TResponseStyle extends ResponseStyle = "fields", + ThrowOnError extends boolean = boolean, + Url extends string = string, > extends Config<{ - responseStyle: TResponseStyle; - throwOnError: ThrowOnError; - }>, - Pick< - ServerSentEventsOptions, - | 'onSseError' - | 'onSseEvent' - | 'sseDefaultRetryDelay' - | 'sseMaxRetryAttempts' - | 'sseMaxRetryDelay' - > { - /** - * Any body that you want to add to your request. - * - * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} - */ - body?: unknown; - path?: Record; - query?: Record; - /** - * Security mechanism(s) to use for the request. - */ - security?: ReadonlyArray; - url: Url; + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + | "onSseError" + | "onSseEvent" + | "sseDefaultRetryDelay" + | "sseMaxRetryAttempts" + | "sseMaxRetryDelay" + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; } export interface ResolvedRequestOptions< - TResponseStyle extends ResponseStyle = 'fields', - ThrowOnError extends boolean = boolean, - Url extends string = string, + TResponseStyle extends ResponseStyle = "fields", + ThrowOnError extends boolean = boolean, + Url extends string = string, > extends RequestOptions { - serializedBody?: string; + serializedBody?: string; } export type RequestResult< - TData = unknown, - TError = unknown, - ThrowOnError extends boolean = boolean, - TResponseStyle extends ResponseStyle = 'fields', + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = "fields", > = ThrowOnError extends true - ? Promise< - TResponseStyle extends 'data' - ? TData extends Record - ? TData[keyof TData] - : TData - : { - data: TData extends Record - ? TData[keyof TData] - : TData; - request: Request; - response: Response; - } - > - : Promise< - TResponseStyle extends 'data' - ? - | (TData extends Record - ? TData[keyof TData] - : TData) - | undefined - : ( - | { - data: TData extends Record - ? TData[keyof TData] - : TData; - error: undefined; - } - | { - data: undefined; - error: TError extends Record - ? TError[keyof TError] - : TError; - } - ) & { - request: Request; - response: Response; - } - >; + ? Promise< + TResponseStyle extends "data" + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends "data" + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; export interface ClientOptions { - baseUrl?: string; - responseStyle?: ResponseStyle; - throwOnError?: boolean; + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; } type MethodFn = < - TData = unknown, - TError = unknown, - ThrowOnError extends boolean = false, - TResponseStyle extends ResponseStyle = 'fields', + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = "fields", >( - options: Omit, 'method'>, + options: Omit, "method">, ) => RequestResult; type SseFn = < - TData = unknown, - TError = unknown, - ThrowOnError extends boolean = false, - TResponseStyle extends ResponseStyle = 'fields', + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = "fields", >( - options: Omit, 'method'>, + options: Omit, "method">, ) => Promise>; type RequestFn = < - TData = unknown, - TError = unknown, - ThrowOnError extends boolean = false, - TResponseStyle extends ResponseStyle = 'fields', + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = "fields", >( - options: Omit, 'method'> & - Pick< - Required>, - 'method' - >, + options: Omit, "method"> & + Pick< + Required>, + "method" + >, ) => RequestResult; type BuildUrlFn = < - TData extends { - body?: unknown; - path?: Record; - query?: Record; - url: string; - }, + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, >( - options: TData & Options, + options: TData & Options, ) => string; export type Client = CoreClient< - RequestFn, - Config, - MethodFn, - BuildUrlFn, - SseFn + RequestFn, + Config, + MethodFn, + BuildUrlFn, + SseFn > & { - interceptors: Middleware; + interceptors: Middleware; }; /** @@ -216,26 +216,26 @@ export type Client = CoreClient< * to ensure your client always has the correct values. */ export type CreateClientConfig = ( - override?: Config, + override?: Config, ) => Config & T>; export interface TDataShape { - body?: unknown; - headers?: unknown; - path?: unknown; - query?: unknown; - url: string; + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; } type OmitKeys = Pick>; export type Options< - TData extends TDataShape = TDataShape, - ThrowOnError extends boolean = boolean, - TResponse = unknown, - TResponseStyle extends ResponseStyle = 'fields', + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = "fields", > = OmitKeys< - RequestOptions, - 'body' | 'path' | 'query' | 'url' + RequestOptions, + "body" | "path" | "query" | "url" > & - ([TData] extends [never] ? unknown : Omit); + ([TData] extends [never] ? unknown : Omit); diff --git a/app/client/api-client/client/utils.gen.ts b/app/client/api-client/client/utils.gen.ts index 4c48a9ee..82b63a89 100644 --- a/app/client/api-client/client/utils.gen.ts +++ b/app/client/api-client/client/utils.gen.ts @@ -1,332 +1,337 @@ // This file is auto-generated by @hey-api/openapi-ts -import { getAuthToken } from '../core/auth.gen'; -import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; -import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { getAuthToken } from "../core/auth.gen"; +import type { QuerySerializerOptions } from "../core/bodySerializer.gen"; +import { jsonBodySerializer } from "../core/bodySerializer.gen"; import { - serializeArrayParam, - serializeObjectParam, - serializePrimitiveParam, -} from '../core/pathSerializer.gen'; -import { getUrl } from '../core/utils.gen'; -import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from "../core/pathSerializer.gen"; +import { getUrl } from "../core/utils.gen"; +import type { + Client, + ClientOptions, + Config, + RequestOptions, +} from "./types.gen"; export const createQuerySerializer = ({ - parameters = {}, - ...args + parameters = {}, + ...args }: QuerySerializerOptions = {}) => { - const querySerializer = (queryParams: T) => { - const search: string[] = []; - if (queryParams && typeof queryParams === 'object') { - for (const name in queryParams) { - const value = queryParams[name]; + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === "object") { + for (const name in queryParams) { + const value = queryParams[name]; - if (value === undefined || value === null) { - continue; - } + if (value === undefined || value === null) { + continue; + } - const options = parameters[name] || args; + const options = parameters[name] || args; - if (Array.isArray(value)) { - const serializedArray = serializeArrayParam({ - allowReserved: options.allowReserved, - explode: true, - name, - style: 'form', - value, - ...options.array, - }); - if (serializedArray) search.push(serializedArray); - } else if (typeof value === 'object') { - const serializedObject = serializeObjectParam({ - allowReserved: options.allowReserved, - explode: true, - name, - style: 'deepObject', - value: value as Record, - ...options.object, - }); - if (serializedObject) search.push(serializedObject); - } else { - const serializedPrimitive = serializePrimitiveParam({ - allowReserved: options.allowReserved, - name, - value: value as string, - }); - if (serializedPrimitive) search.push(serializedPrimitive); - } - } - } - return search.join('&'); - }; - return querySerializer; + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved: options.allowReserved, + explode: true, + name, + style: "form", + value, + ...options.array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === "object") { + const serializedObject = serializeObjectParam({ + allowReserved: options.allowReserved, + explode: true, + name, + style: "deepObject", + value: value as Record, + ...options.object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved: options.allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join("&"); + }; + return querySerializer; }; /** * Infers parseAs value from provided Content-Type header. */ export const getParseAs = ( - contentType: string | null, -): Exclude => { - if (!contentType) { - // If no Content-Type header is provided, the best we can do is return the raw response body, - // which is effectively the same as the 'stream' option. - return 'stream'; - } + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return "stream"; + } - const cleanContent = contentType.split(';')[0]?.trim(); + const cleanContent = contentType.split(";")[0]?.trim(); - if (!cleanContent) { - return; - } + if (!cleanContent) { + return; + } - if ( - cleanContent.startsWith('application/json') || - cleanContent.endsWith('+json') - ) { - return 'json'; - } + if ( + cleanContent.startsWith("application/json") || + cleanContent.endsWith("+json") + ) { + return "json"; + } - if (cleanContent === 'multipart/form-data') { - return 'formData'; - } + if (cleanContent === "multipart/form-data") { + return "formData"; + } - if ( - ['application/', 'audio/', 'image/', 'video/'].some((type) => - cleanContent.startsWith(type), - ) - ) { - return 'blob'; - } + if ( + ["application/", "audio/", "image/", "video/"].some((type) => + cleanContent.startsWith(type), + ) + ) { + return "blob"; + } - if (cleanContent.startsWith('text/')) { - return 'text'; - } + if (cleanContent.startsWith("text/")) { + return "text"; + } - return; + return; }; const checkForExistence = ( - options: Pick & { - headers: Headers; - }, - name?: string, + options: Pick & { + headers: Headers; + }, + name?: string, ): boolean => { - if (!name) { - return false; - } - if ( - options.headers.has(name) || - options.query?.[name] || - options.headers.get('Cookie')?.includes(`${name}=`) - ) { - return true; - } - return false; + if (!name) { + return false; + } + if ( + options.headers.has(name) || + options.query?.[name] || + options.headers.get("Cookie")?.includes(`${name}=`) + ) { + return true; + } + return false; }; export const setAuthParams = async ({ - security, - ...options -}: Pick, 'security'> & - Pick & { - headers: Headers; - }) => { - for (const auth of security) { - if (checkForExistence(options, auth.name)) { - continue; - } + security, + ...options +}: Pick, "security"> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue; + } - const token = await getAuthToken(auth, options.auth); + const token = await getAuthToken(auth, options.auth); - if (!token) { - continue; - } + if (!token) { + continue; + } - const name = auth.name ?? 'Authorization'; + const name = auth.name ?? "Authorization"; - switch (auth.in) { - case 'query': - if (!options.query) { - options.query = {}; - } - options.query[name] = token; - break; - case 'cookie': - options.headers.append('Cookie', `${name}=${token}`); - break; - case 'header': - default: - options.headers.set(name, token); - break; - } - } + switch (auth.in) { + case "query": + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case "cookie": + options.headers.append("Cookie", `${name}=${token}`); + break; + case "header": + default: + options.headers.set(name, token); + break; + } + } }; -export const buildUrl: Client['buildUrl'] = (options) => - getUrl({ - baseUrl: options.baseUrl as string, - path: options.path, - query: options.query, - querySerializer: - typeof options.querySerializer === 'function' - ? options.querySerializer - : createQuerySerializer(options.querySerializer), - url: options.url, - }); +export const buildUrl: Client["buildUrl"] = (options) => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === "function" + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); export const mergeConfigs = (a: Config, b: Config): Config => { - const config = { ...a, ...b }; - if (config.baseUrl?.endsWith('/')) { - config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); - } - config.headers = mergeHeaders(a.headers, b.headers); - return config; + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith("/")) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; }; const headersEntries = (headers: Headers): Array<[string, string]> => { - const entries: Array<[string, string]> = []; - headers.forEach((value, key) => { - entries.push([key, value]); - }); - return entries; + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; }; export const mergeHeaders = ( - ...headers: Array['headers'] | undefined> + ...headers: Array["headers"] | undefined> ): Headers => { - const mergedHeaders = new Headers(); - for (const header of headers) { - if (!header) { - continue; - } + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } - const iterator = - header instanceof Headers - ? headersEntries(header) - : Object.entries(header); + const iterator = + header instanceof Headers + ? headersEntries(header) + : Object.entries(header); - for (const [key, value] of iterator) { - if (value === null) { - mergedHeaders.delete(key); - } else if (Array.isArray(value)) { - for (const v of value) { - mergedHeaders.append(key, v as string); - } - } else if (value !== undefined) { - // assume object headers are meant to be JSON stringified, i.e. their - // content value in OpenAPI specification is 'application/json' - mergedHeaders.set( - key, - typeof value === 'object' ? JSON.stringify(value) : (value as string), - ); - } - } - } - return mergedHeaders; + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === "object" ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; }; type ErrInterceptor = ( - error: Err, - response: Res, - request: Req, - options: Options, + error: Err, + response: Res, + request: Req, + options: Options, ) => Err | Promise; type ReqInterceptor = ( - request: Req, - options: Options, + request: Req, + options: Options, ) => Req | Promise; type ResInterceptor = ( - response: Res, - request: Req, - options: Options, + response: Res, + request: Req, + options: Options, ) => Res | Promise; class Interceptors { - fns: Array = []; + fns: Array = []; - clear(): void { - this.fns = []; - } + clear(): void { + this.fns = []; + } - eject(id: number | Interceptor): void { - const index = this.getInterceptorIndex(id); - if (this.fns[index]) { - this.fns[index] = null; - } - } + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } - exists(id: number | Interceptor): boolean { - const index = this.getInterceptorIndex(id); - return Boolean(this.fns[index]); - } + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } - getInterceptorIndex(id: number | Interceptor): number { - if (typeof id === 'number') { - return this.fns[id] ? id : -1; - } - return this.fns.indexOf(id); - } + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === "number") { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } - update( - id: number | Interceptor, - fn: Interceptor, - ): number | Interceptor | false { - const index = this.getInterceptorIndex(id); - if (this.fns[index]) { - this.fns[index] = fn; - return id; - } - return false; - } + update( + id: number | Interceptor, + fn: Interceptor, + ): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } - use(fn: Interceptor): number { - this.fns.push(fn); - return this.fns.length - 1; - } + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } } export interface Middleware { - error: Interceptors>; - request: Interceptors>; - response: Interceptors>; + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; } export const createInterceptors = (): Middleware< - Req, - Res, - Err, - Options + Req, + Res, + Err, + Options > => ({ - error: new Interceptors>(), - request: new Interceptors>(), - response: new Interceptors>(), + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), }); const defaultQuerySerializer = createQuerySerializer({ - allowReserved: false, - array: { - explode: true, - style: 'form', - }, - object: { - explode: true, - style: 'deepObject', - }, + allowReserved: false, + array: { + explode: true, + style: "form", + }, + object: { + explode: true, + style: "deepObject", + }, }); const defaultHeaders = { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }; export const createConfig = ( - override: Config & T> = {}, + override: Config & T> = {}, ): Config & T> => ({ - ...jsonBodySerializer, - headers: defaultHeaders, - parseAs: 'auto', - querySerializer: defaultQuerySerializer, - ...override, + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: "auto", + querySerializer: defaultQuerySerializer, + ...override, }); diff --git a/app/client/api-client/core/auth.gen.ts b/app/client/api-client/core/auth.gen.ts index f8a73266..48eb5109 100644 --- a/app/client/api-client/core/auth.gen.ts +++ b/app/client/api-client/core/auth.gen.ts @@ -3,40 +3,40 @@ export type AuthToken = string | undefined; export interface Auth { - /** - * Which part of the request do we use to send the auth? - * - * @default 'header' - */ - in?: 'header' | 'query' | 'cookie'; - /** - * Header or query parameter name. - * - * @default 'Authorization' - */ - name?: string; - scheme?: 'basic' | 'bearer'; - type: 'apiKey' | 'http'; + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: "header" | "query" | "cookie"; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: "basic" | "bearer"; + type: "apiKey" | "http"; } export const getAuthToken = async ( - auth: Auth, - callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, ): Promise => { - const token = - typeof callback === 'function' ? await callback(auth) : callback; + const token = + typeof callback === "function" ? await callback(auth) : callback; - if (!token) { - return; - } + if (!token) { + return; + } - if (auth.scheme === 'bearer') { - return `Bearer ${token}`; - } + if (auth.scheme === "bearer") { + return `Bearer ${token}`; + } - if (auth.scheme === 'basic') { - return `Basic ${btoa(token)}`; - } + if (auth.scheme === "basic") { + return `Basic ${btoa(token)}`; + } - return token; + return token; }; diff --git a/app/client/api-client/core/bodySerializer.gen.ts b/app/client/api-client/core/bodySerializer.gen.ts index 552b50f7..80771d70 100644 --- a/app/client/api-client/core/bodySerializer.gen.ts +++ b/app/client/api-client/core/bodySerializer.gen.ts @@ -1,100 +1,100 @@ // This file is auto-generated by @hey-api/openapi-ts import type { - ArrayStyle, - ObjectStyle, - SerializerOptions, -} from './pathSerializer.gen'; + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from "./pathSerializer.gen"; export type QuerySerializer = (query: Record) => string; export type BodySerializer = (body: any) => any; type QuerySerializerOptionsObject = { - allowReserved?: boolean; - array?: Partial>; - object?: Partial>; + allowReserved?: boolean; + array?: Partial>; + object?: Partial>; }; export type QuerySerializerOptions = QuerySerializerOptionsObject & { - /** - * Per-parameter serialization overrides. When provided, these settings - * override the global array/object settings for specific parameter names. - */ - parameters?: Record; + /** + * Per-parameter serialization overrides. When provided, these settings + * override the global array/object settings for specific parameter names. + */ + parameters?: Record; }; const serializeFormDataPair = ( - data: FormData, - key: string, - value: unknown, + data: FormData, + key: string, + value: unknown, ): void => { - if (typeof value === 'string' || value instanceof Blob) { - data.append(key, value); - } else if (value instanceof Date) { - data.append(key, value.toISOString()); - } else { - data.append(key, JSON.stringify(value)); - } + if (typeof value === "string" || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } }; const serializeUrlSearchParamsPair = ( - data: URLSearchParams, - key: string, - value: unknown, + data: URLSearchParams, + key: string, + value: unknown, ): void => { - if (typeof value === 'string') { - data.append(key, value); - } else { - data.append(key, JSON.stringify(value)); - } + if (typeof value === "string") { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } }; export const formDataBodySerializer = { - bodySerializer: | Array>>( - body: T, - ): FormData => { - const data = new FormData(); + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); - Object.entries(body).forEach(([key, value]) => { - if (value === undefined || value === null) { - return; - } - if (Array.isArray(value)) { - value.forEach((v) => serializeFormDataPair(data, key, v)); - } else { - serializeFormDataPair(data, key, value); - } - }); + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); - return data; - }, + return data; + }, }; export const jsonBodySerializer = { - bodySerializer: (body: T): string => - JSON.stringify(body, (_key, value) => - typeof value === 'bigint' ? value.toString() : value, - ), + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => + typeof value === "bigint" ? value.toString() : value, + ), }; export const urlSearchParamsBodySerializer = { - bodySerializer: | Array>>( - body: T, - ): string => { - const data = new URLSearchParams(); + bodySerializer: | Array>>( + body: T, + ): string => { + const data = new URLSearchParams(); - Object.entries(body).forEach(([key, value]) => { - if (value === undefined || value === null) { - return; - } - if (Array.isArray(value)) { - value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); - } else { - serializeUrlSearchParamsPair(data, key, value); - } - }); + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); - return data.toString(); - }, + return data.toString(); + }, }; diff --git a/app/client/api-client/core/params.gen.ts b/app/client/api-client/core/params.gen.ts index 602715c4..8cfe5a59 100644 --- a/app/client/api-client/core/params.gen.ts +++ b/app/client/api-client/core/params.gen.ts @@ -1,176 +1,176 @@ // This file is auto-generated by @hey-api/openapi-ts -type Slot = 'body' | 'headers' | 'path' | 'query'; +type Slot = "body" | "headers" | "path" | "query"; export type Field = - | { - in: Exclude; - /** - * Field name. This is the name we want the user to see and use. - */ - key: string; - /** - * Field mapped name. This is the name we want to use in the request. - * If omitted, we use the same value as `key`. - */ - map?: string; - } - | { - in: Extract; - /** - * Key isn't required for bodies. - */ - key?: string; - map?: string; - } - | { - /** - * Field name. This is the name we want the user to see and use. - */ - key: string; - /** - * Field mapped name. This is the name we want to use in the request. - * If `in` is omitted, `map` aliases `key` to the transport layer. - */ - map: Slot; - }; + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + } + | { + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If `in` is omitted, `map` aliases `key` to the transport layer. + */ + map: Slot; + }; export interface Fields { - allowExtra?: Partial>; - args?: ReadonlyArray; + allowExtra?: Partial>; + args?: ReadonlyArray; } export type FieldsConfig = ReadonlyArray; const extraPrefixesMap: Record = { - $body_: 'body', - $headers_: 'headers', - $path_: 'path', - $query_: 'query', + $body_: "body", + $headers_: "headers", + $path_: "path", + $query_: "query", }; const extraPrefixes = Object.entries(extraPrefixesMap); type KeyMap = Map< - string, - | { - in: Slot; - map?: string; - } - | { - in?: never; - map: Slot; - } + string, + | { + in: Slot; + map?: string; + } + | { + in?: never; + map: Slot; + } >; const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { - if (!map) { - map = new Map(); - } + if (!map) { + map = new Map(); + } - for (const config of fields) { - if ('in' in config) { - if (config.key) { - map.set(config.key, { - in: config.in, - map: config.map, - }); - } - } else if ('key' in config) { - map.set(config.key, { - map: config.map, - }); - } else if (config.args) { - buildKeyMap(config.args, map); - } - } + for (const config of fields) { + if ("in" in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if ("key" in config) { + map.set(config.key, { + map: config.map, + }); + } else if (config.args) { + buildKeyMap(config.args, map); + } + } - return map; + return map; }; interface Params { - body: unknown; - headers: Record; - path: Record; - query: Record; + body: unknown; + headers: Record; + path: Record; + query: Record; } const stripEmptySlots = (params: Params) => { - for (const [slot, value] of Object.entries(params)) { - if (value && typeof value === 'object' && !Object.keys(value).length) { - delete params[slot as Slot]; - } - } + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === "object" && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } }; export const buildClientParams = ( - args: ReadonlyArray, - fields: FieldsConfig, + args: ReadonlyArray, + fields: FieldsConfig, ) => { - const params: Params = { - body: {}, - headers: {}, - path: {}, - query: {}, - }; + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; - const map = buildKeyMap(fields); + const map = buildKeyMap(fields); - let config: FieldsConfig[number] | undefined; + let config: FieldsConfig[number] | undefined; - for (const [index, arg] of args.entries()) { - if (fields[index]) { - config = fields[index]; - } + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } - if (!config) { - continue; - } + if (!config) { + continue; + } - if ('in' in config) { - if (config.key) { - const field = map.get(config.key)!; - const name = field.map || config.key; - if (field.in) { - (params[field.in] as Record)[name] = arg; - } - } else { - params.body = arg; - } - } else { - for (const [key, value] of Object.entries(arg ?? {})) { - const field = map.get(key); + if ("in" in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + if (field.in) { + (params[field.in] as Record)[name] = arg; + } + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); - if (field) { - if (field.in) { - const name = field.map || key; - (params[field.in] as Record)[name] = value; - } else { - params[field.map] = value; - } - } else { - const extra = extraPrefixes.find(([prefix]) => - key.startsWith(prefix), - ); + if (field) { + if (field.in) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + params[field.map] = value; + } + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); - if (extra) { - const [prefix, slot] = extra; - (params[slot] as Record)[ - key.slice(prefix.length) - ] = value; - } else if ('allowExtra' in config && config.allowExtra) { - for (const [slot, allowed] of Object.entries(config.allowExtra)) { - if (allowed) { - (params[slot as Slot] as Record)[key] = value; - break; - } - } - } - } - } - } - } + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else if ("allowExtra" in config && config.allowExtra) { + for (const [slot, allowed] of Object.entries(config.allowExtra)) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } - stripEmptySlots(params); + stripEmptySlots(params); - return params; + return params; }; diff --git a/app/client/api-client/core/pathSerializer.gen.ts b/app/client/api-client/core/pathSerializer.gen.ts index 8d999310..2fe930d9 100644 --- a/app/client/api-client/core/pathSerializer.gen.ts +++ b/app/client/api-client/core/pathSerializer.gen.ts @@ -1,181 +1,181 @@ // This file is auto-generated by @hey-api/openapi-ts interface SerializeOptions - extends SerializePrimitiveOptions, - SerializerOptions {} + extends SerializePrimitiveOptions, + SerializerOptions {} interface SerializePrimitiveOptions { - allowReserved?: boolean; - name: string; + allowReserved?: boolean; + name: string; } export interface SerializerOptions { - /** - * @default true - */ - explode: boolean; - style: T; + /** + * @default true + */ + explode: boolean; + style: T; } -export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArrayStyle = "form" | "spaceDelimited" | "pipeDelimited"; export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; -type MatrixStyle = 'label' | 'matrix' | 'simple'; -export type ObjectStyle = 'form' | 'deepObject'; +type MatrixStyle = "label" | "matrix" | "simple"; +export type ObjectStyle = "form" | "deepObject"; type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; interface SerializePrimitiveParam extends SerializePrimitiveOptions { - value: string; + value: string; } export const separatorArrayExplode = (style: ArraySeparatorStyle) => { - switch (style) { - case 'label': - return '.'; - case 'matrix': - return ';'; - case 'simple': - return ','; - default: - return '&'; - } + switch (style) { + case "label": + return "."; + case "matrix": + return ";"; + case "simple": + return ","; + default: + return "&"; + } }; export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { - switch (style) { - case 'form': - return ','; - case 'pipeDelimited': - return '|'; - case 'spaceDelimited': - return '%20'; - default: - return ','; - } + switch (style) { + case "form": + return ","; + case "pipeDelimited": + return "|"; + case "spaceDelimited": + return "%20"; + default: + return ","; + } }; export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { - switch (style) { - case 'label': - return '.'; - case 'matrix': - return ';'; - case 'simple': - return ','; - default: - return '&'; - } + switch (style) { + case "label": + return "."; + case "matrix": + return ";"; + case "simple": + return ","; + default: + return "&"; + } }; export const serializeArrayParam = ({ - allowReserved, - explode, - name, - style, - value, + allowReserved, + explode, + name, + style, + value, }: SerializeOptions & { - value: unknown[]; + value: unknown[]; }) => { - if (!explode) { - const joinedValues = ( - allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) - ).join(separatorArrayNoExplode(style)); - switch (style) { - case 'label': - return `.${joinedValues}`; - case 'matrix': - return `;${name}=${joinedValues}`; - case 'simple': - return joinedValues; - default: - return `${name}=${joinedValues}`; - } - } + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case "label": + return `.${joinedValues}`; + case "matrix": + return `;${name}=${joinedValues}`; + case "simple": + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } - const separator = separatorArrayExplode(style); - const joinedValues = value - .map((v) => { - if (style === 'label' || style === 'simple') { - return allowReserved ? v : encodeURIComponent(v as string); - } + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === "label" || style === "simple") { + return allowReserved ? v : encodeURIComponent(v as string); + } - return serializePrimitiveParam({ - allowReserved, - name, - value: v as string, - }); - }) - .join(separator); - return style === 'label' || style === 'matrix' - ? separator + joinedValues - : joinedValues; + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === "label" || style === "matrix" + ? separator + joinedValues + : joinedValues; }; export const serializePrimitiveParam = ({ - allowReserved, - name, - value, + allowReserved, + name, + value, }: SerializePrimitiveParam) => { - if (value === undefined || value === null) { - return ''; - } + if (value === undefined || value === null) { + return ""; + } - if (typeof value === 'object') { - throw new Error( - 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', - ); - } + if (typeof value === "object") { + throw new Error( + "Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.", + ); + } - return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; }; export const serializeObjectParam = ({ - allowReserved, - explode, - name, - style, - value, - valueOnly, + allowReserved, + explode, + name, + style, + value, + valueOnly, }: SerializeOptions & { - value: Record | Date; - valueOnly?: boolean; + value: Record | Date; + valueOnly?: boolean; }) => { - if (value instanceof Date) { - return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; - } + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } - if (style !== 'deepObject' && !explode) { - let values: string[] = []; - Object.entries(value).forEach(([key, v]) => { - values = [ - ...values, - key, - allowReserved ? (v as string) : encodeURIComponent(v as string), - ]; - }); - const joinedValues = values.join(','); - switch (style) { - case 'form': - return `${name}=${joinedValues}`; - case 'label': - return `.${joinedValues}`; - case 'matrix': - return `;${name}=${joinedValues}`; - default: - return joinedValues; - } - } + if (style !== "deepObject" && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(","); + switch (style) { + case "form": + return `${name}=${joinedValues}`; + case "label": + return `.${joinedValues}`; + case "matrix": + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } - const separator = separatorObjectExplode(style); - const joinedValues = Object.entries(value) - .map(([key, v]) => - serializePrimitiveParam({ - allowReserved, - name: style === 'deepObject' ? `${name}[${key}]` : key, - value: v as string, - }), - ) - .join(separator); - return style === 'label' || style === 'matrix' - ? separator + joinedValues - : joinedValues; + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === "deepObject" ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === "label" || style === "matrix" + ? separator + joinedValues + : joinedValues; }; diff --git a/app/client/api-client/core/queryKeySerializer.gen.ts b/app/client/api-client/core/queryKeySerializer.gen.ts index d3bb6839..2e8e7aab 100644 --- a/app/client/api-client/core/queryKeySerializer.gen.ts +++ b/app/client/api-client/core/queryKeySerializer.gen.ts @@ -4,133 +4,133 @@ * JSON-friendly union that mirrors what Pinia Colada can hash. */ export type JsonValue = - | null - | string - | number - | boolean - | JsonValue[] - | { [key: string]: JsonValue }; + | null + | string + | number + | boolean + | JsonValue[] + | { [key: string]: JsonValue }; /** * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes. */ export const queryKeyJsonReplacer = (_key: string, value: unknown) => { - if ( - value === undefined || - typeof value === 'function' || - typeof value === 'symbol' - ) { - return undefined; - } - if (typeof value === 'bigint') { - return value.toString(); - } - if (value instanceof Date) { - return value.toISOString(); - } - return value; + if ( + value === undefined || + typeof value === "function" || + typeof value === "symbol" + ) { + return undefined; + } + if (typeof value === "bigint") { + return value.toString(); + } + if (value instanceof Date) { + return value.toISOString(); + } + return value; }; /** * Safely stringifies a value and parses it back into a JsonValue. */ export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => { - try { - const json = JSON.stringify(input, queryKeyJsonReplacer); - if (json === undefined) { - return undefined; - } - return JSON.parse(json) as JsonValue; - } catch { - return undefined; - } + try { + const json = JSON.stringify(input, queryKeyJsonReplacer); + if (json === undefined) { + return undefined; + } + return JSON.parse(json) as JsonValue; + } catch { + return undefined; + } }; /** * Detects plain objects (including objects with a null prototype). */ const isPlainObject = (value: unknown): value is Record => { - if (value === null || typeof value !== 'object') { - return false; - } - const prototype = Object.getPrototypeOf(value as object); - return prototype === Object.prototype || prototype === null; + if (value === null || typeof value !== "object") { + return false; + } + const prototype = Object.getPrototypeOf(value as object); + return prototype === Object.prototype || prototype === null; }; /** * Turns URLSearchParams into a sorted JSON object for deterministic keys. */ const serializeSearchParams = (params: URLSearchParams): JsonValue => { - const entries = Array.from(params.entries()).sort(([a], [b]) => - a.localeCompare(b), - ); - const result: Record = {}; + const entries = Array.from(params.entries()).sort(([a], [b]) => + a.localeCompare(b), + ); + const result: Record = {}; - for (const [key, value] of entries) { - const existing = result[key]; - if (existing === undefined) { - result[key] = value; - continue; - } + for (const [key, value] of entries) { + const existing = result[key]; + if (existing === undefined) { + result[key] = value; + continue; + } - if (Array.isArray(existing)) { - (existing as string[]).push(value); - } else { - result[key] = [existing, value]; - } - } + if (Array.isArray(existing)) { + (existing as string[]).push(value); + } else { + result[key] = [existing, value]; + } + } - return result; + return result; }; /** * Normalizes any accepted value into a JSON-friendly shape for query keys. */ export const serializeQueryKeyValue = ( - value: unknown, + value: unknown, ): JsonValue | undefined => { - if (value === null) { - return null; - } + if (value === null) { + return null; + } - if ( - typeof value === 'string' || - typeof value === 'number' || - typeof value === 'boolean' - ) { - return value; - } + if ( + typeof value === "string" || + typeof value === "number" || + typeof value === "boolean" + ) { + return value; + } - if ( - value === undefined || - typeof value === 'function' || - typeof value === 'symbol' - ) { - return undefined; - } + if ( + value === undefined || + typeof value === "function" || + typeof value === "symbol" + ) { + return undefined; + } - if (typeof value === 'bigint') { - return value.toString(); - } + if (typeof value === "bigint") { + return value.toString(); + } - if (value instanceof Date) { - return value.toISOString(); - } + if (value instanceof Date) { + return value.toISOString(); + } - if (Array.isArray(value)) { - return stringifyToJsonValue(value); - } + if (Array.isArray(value)) { + return stringifyToJsonValue(value); + } - if ( - typeof URLSearchParams !== 'undefined' && - value instanceof URLSearchParams - ) { - return serializeSearchParams(value); - } + if ( + typeof URLSearchParams !== "undefined" && + value instanceof URLSearchParams + ) { + return serializeSearchParams(value); + } - if (isPlainObject(value)) { - return stringifyToJsonValue(value); - } + if (isPlainObject(value)) { + return stringifyToJsonValue(value); + } - return undefined; + return undefined; }; diff --git a/app/client/api-client/core/serverSentEvents.gen.ts b/app/client/api-client/core/serverSentEvents.gen.ts index 343d25af..8dfe6a5c 100644 --- a/app/client/api-client/core/serverSentEvents.gen.ts +++ b/app/client/api-client/core/serverSentEvents.gen.ts @@ -1,266 +1,266 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Config } from './types.gen'; +import type { Config } from "./types.gen"; export type ServerSentEventsOptions = Omit< - RequestInit, - 'method' + RequestInit, + "method" > & - Pick & { - /** - * Fetch API implementation. You can use this option to provide a custom - * fetch instance. - * - * @default globalThis.fetch - */ - fetch?: typeof fetch; - /** - * Implementing clients can call request interceptors inside this hook. - */ - onRequest?: (url: string, init: RequestInit) => Promise; - /** - * Callback invoked when a network or parsing error occurs during streaming. - * - * This option applies only if the endpoint returns a stream of events. - * - * @param error The error that occurred. - */ - onSseError?: (error: unknown) => void; - /** - * Callback invoked when an event is streamed from the server. - * - * This option applies only if the endpoint returns a stream of events. - * - * @param event Event streamed from the server. - * @returns Nothing (void). - */ - onSseEvent?: (event: StreamEvent) => void; - serializedBody?: RequestInit['body']; - /** - * Default retry delay in milliseconds. - * - * This option applies only if the endpoint returns a stream of events. - * - * @default 3000 - */ - sseDefaultRetryDelay?: number; - /** - * Maximum number of retry attempts before giving up. - */ - sseMaxRetryAttempts?: number; - /** - * Maximum retry delay in milliseconds. - * - * Applies only when exponential backoff is used. - * - * This option applies only if the endpoint returns a stream of events. - * - * @default 30000 - */ - sseMaxRetryDelay?: number; - /** - * Optional sleep function for retry backoff. - * - * Defaults to using `setTimeout`. - */ - sseSleepFn?: (ms: number) => Promise; - url: string; - }; + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit["body"]; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; export interface StreamEvent { - data: TData; - event?: string; - id?: string; - retry?: number; + data: TData; + event?: string; + id?: string; + retry?: number; } export type ServerSentEventsResult< - TData = unknown, - TReturn = void, - TNext = unknown, + TData = unknown, + TReturn = void, + TNext = unknown, > = { - stream: AsyncGenerator< - TData extends Record ? TData[keyof TData] : TData, - TReturn, - TNext - >; + stream: AsyncGenerator< + TData extends Record ? TData[keyof TData] : TData, + TReturn, + TNext + >; }; export const createSseClient = ({ - onRequest, - onSseError, - onSseEvent, - responseTransformer, - responseValidator, - sseDefaultRetryDelay, - sseMaxRetryAttempts, - sseMaxRetryDelay, - sseSleepFn, - url, - ...options + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options }: ServerSentEventsOptions): ServerSentEventsResult => { - let lastEventId: string | undefined; + let lastEventId: string | undefined; - const sleep = - sseSleepFn ?? - ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); + const sleep = + sseSleepFn ?? + ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); - const createStream = async function* () { - let retryDelay: number = sseDefaultRetryDelay ?? 3000; - let attempt = 0; - const signal = options.signal ?? new AbortController().signal; + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; - while (true) { - if (signal.aborted) break; + while (true) { + if (signal.aborted) break; - attempt++; + attempt++; - const headers = - options.headers instanceof Headers - ? options.headers - : new Headers(options.headers as Record | undefined); + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); - if (lastEventId !== undefined) { - headers.set('Last-Event-ID', lastEventId); - } + if (lastEventId !== undefined) { + headers.set("Last-Event-ID", lastEventId); + } - try { - const requestInit: RequestInit = { - redirect: 'follow', - ...options, - body: options.serializedBody, - headers, - signal, - }; - let request = new Request(url, requestInit); - if (onRequest) { - request = await onRequest(url, requestInit); - } - // fetch must be assigned here, otherwise it would throw the error: - // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation - const _fetch = options.fetch ?? globalThis.fetch; - const response = await _fetch(request); + try { + const requestInit: RequestInit = { + redirect: "follow", + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); - if (!response.ok) - throw new Error( - `SSE failed: ${response.status} ${response.statusText}`, - ); + if (!response.ok) + throw new Error( + `SSE failed: ${response.status} ${response.statusText}`, + ); - if (!response.body) throw new Error('No body in SSE response'); + if (!response.body) throw new Error("No body in SSE response"); - const reader = response.body - .pipeThrough(new TextDecoderStream()) - .getReader(); + const reader = response.body + .pipeThrough(new TextDecoderStream()) + .getReader(); - let buffer = ''; + let buffer = ""; - const abortHandler = () => { - try { - reader.cancel(); - } catch { - // noop - } - }; + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; - signal.addEventListener('abort', abortHandler); + signal.addEventListener("abort", abortHandler); - try { - while (true) { - const { done, value } = await reader.read(); - if (done) break; - buffer += value; - // Normalize line endings: CRLF -> LF, then CR -> LF - buffer = buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + // Normalize line endings: CRLF -> LF, then CR -> LF + buffer = buffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); - const chunks = buffer.split('\n\n'); - buffer = chunks.pop() ?? ''; + const chunks = buffer.split("\n\n"); + buffer = chunks.pop() ?? ""; - for (const chunk of chunks) { - const lines = chunk.split('\n'); - const dataLines: Array = []; - let eventName: string | undefined; + for (const chunk of chunks) { + const lines = chunk.split("\n"); + const dataLines: Array = []; + let eventName: string | undefined; - for (const line of lines) { - if (line.startsWith('data:')) { - dataLines.push(line.replace(/^data:\s*/, '')); - } else if (line.startsWith('event:')) { - eventName = line.replace(/^event:\s*/, ''); - } else if (line.startsWith('id:')) { - lastEventId = line.replace(/^id:\s*/, ''); - } else if (line.startsWith('retry:')) { - const parsed = Number.parseInt( - line.replace(/^retry:\s*/, ''), - 10, - ); - if (!Number.isNaN(parsed)) { - retryDelay = parsed; - } - } - } + for (const line of lines) { + if (line.startsWith("data:")) { + dataLines.push(line.replace(/^data:\s*/, "")); + } else if (line.startsWith("event:")) { + eventName = line.replace(/^event:\s*/, ""); + } else if (line.startsWith("id:")) { + lastEventId = line.replace(/^id:\s*/, ""); + } else if (line.startsWith("retry:")) { + const parsed = Number.parseInt( + line.replace(/^retry:\s*/, ""), + 10, + ); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } - let data: unknown; - let parsedJson = false; + let data: unknown; + let parsedJson = false; - if (dataLines.length) { - const rawData = dataLines.join('\n'); - try { - data = JSON.parse(rawData); - parsedJson = true; - } catch { - data = rawData; - } - } + if (dataLines.length) { + const rawData = dataLines.join("\n"); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } - if (parsedJson) { - if (responseValidator) { - await responseValidator(data); - } + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } - if (responseTransformer) { - data = await responseTransformer(data); - } - } + if (responseTransformer) { + data = await responseTransformer(data); + } + } - onSseEvent?.({ - data, - event: eventName, - id: lastEventId, - retry: retryDelay, - }); + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); - if (dataLines.length) { - yield data as any; - } - } - } - } finally { - signal.removeEventListener('abort', abortHandler); - reader.releaseLock(); - } + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener("abort", abortHandler); + reader.releaseLock(); + } - break; // exit loop on normal completion - } catch (error) { - // connection failed or aborted; retry after delay - onSseError?.(error); + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); - if ( - sseMaxRetryAttempts !== undefined && - attempt >= sseMaxRetryAttempts - ) { - break; // stop after firing error - } + if ( + sseMaxRetryAttempts !== undefined && + attempt >= sseMaxRetryAttempts + ) { + break; // stop after firing error + } - // exponential backoff: double retry each attempt, cap at 30s - const backoff = Math.min( - retryDelay * 2 ** (attempt - 1), - sseMaxRetryDelay ?? 30000, - ); - await sleep(backoff); - } - } - }; + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min( + retryDelay * 2 ** (attempt - 1), + sseMaxRetryDelay ?? 30000, + ); + await sleep(backoff); + } + } + }; - const stream = createStream(); + const stream = createStream(); - return { stream }; + return { stream }; }; diff --git a/app/client/api-client/core/types.gen.ts b/app/client/api-client/core/types.gen.ts index 643c070c..8db8c934 100644 --- a/app/client/api-client/core/types.gen.ts +++ b/app/client/api-client/core/types.gen.ts @@ -1,118 +1,118 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Auth, AuthToken } from './auth.gen'; +import type { Auth, AuthToken } from "./auth.gen"; import type { - BodySerializer, - QuerySerializer, - QuerySerializerOptions, -} from './bodySerializer.gen'; + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from "./bodySerializer.gen"; export type HttpMethod = - | 'connect' - | 'delete' - | 'get' - | 'head' - | 'options' - | 'patch' - | 'post' - | 'put' - | 'trace'; + | "connect" + | "delete" + | "get" + | "head" + | "options" + | "patch" + | "post" + | "put" + | "trace"; export type Client< - RequestFn = never, - Config = unknown, - MethodFn = never, - BuildUrlFn = never, - SseFn = never, + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, + SseFn = never, > = { - /** - * Returns the final request URL. - */ - buildUrl: BuildUrlFn; - getConfig: () => Config; - request: RequestFn; - setConfig: (config: Config) => Config; + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; } & { - [K in HttpMethod]: MethodFn; + [K in HttpMethod]: MethodFn; } & ([SseFn] extends [never] - ? { sse?: never } - : { sse: { [K in HttpMethod]: SseFn } }); + ? { sse?: never } + : { sse: { [K in HttpMethod]: SseFn } }); export interface Config { - /** - * Auth token or a function returning auth token. The resolved value will be - * added to the request payload as defined by its `security` array. - */ - auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; - /** - * A function for serializing request body parameter. By default, - * {@link JSON.stringify()} will be used. - */ - bodySerializer?: BodySerializer | null; - /** - * An object containing any HTTP headers that you want to pre-populate your - * `Headers` object with. - * - * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} - */ - headers?: - | RequestInit['headers'] - | Record< - string, - | string - | number - | boolean - | (string | number | boolean)[] - | null - | undefined - | unknown - >; - /** - * The request method. - * - * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} - */ - method?: Uppercase; - /** - * A function for serializing request query parameters. By default, arrays - * will be exploded in form style, objects will be exploded in deepObject - * style, and reserved characters are percent-encoded. - * - * This method will have no effect if the native `paramsSerializer()` Axios - * API function is used. - * - * {@link https://swagger.io/docs/specification/serialization/#query View examples} - */ - querySerializer?: QuerySerializer | QuerySerializerOptions; - /** - * A function validating request data. This is useful if you want to ensure - * the request conforms to the desired shape, so it can be safely sent to - * the server. - */ - requestValidator?: (data: unknown) => Promise; - /** - * A function transforming response data before it's returned. This is useful - * for post-processing data, e.g. converting ISO strings into Date objects. - */ - responseTransformer?: (data: unknown) => Promise; - /** - * A function validating response data. This is useful if you want to ensure - * the response conforms to the desired shape, so it can be safely passed to - * the transformers and returned to the user. - */ - responseValidator?: (data: unknown) => Promise; + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit["headers"] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; } type IsExactlyNeverOrNeverUndefined = [T] extends [never] - ? true - : [T] extends [never | undefined] - ? [undefined] extends [T] - ? false - : true - : false; + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; export type OmitNever> = { - [K in keyof T as IsExactlyNeverOrNeverUndefined extends true - ? never - : K]: T[K]; + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true + ? never + : K]: T[K]; }; diff --git a/app/client/api-client/core/utils.gen.ts b/app/client/api-client/core/utils.gen.ts index 0b5389d0..999c4a36 100644 --- a/app/client/api-client/core/utils.gen.ts +++ b/app/client/api-client/core/utils.gen.ts @@ -1,143 +1,143 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import type { BodySerializer, QuerySerializer } from "./bodySerializer.gen"; import { - type ArraySeparatorStyle, - serializeArrayParam, - serializeObjectParam, - serializePrimitiveParam, -} from './pathSerializer.gen'; + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from "./pathSerializer.gen"; export interface PathSerializer { - path: Record; - url: string; + path: Record; + url: string; } export const PATH_PARAM_RE = /\{[^{}]+\}/g; export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { - let url = _url; - const matches = _url.match(PATH_PARAM_RE); - if (matches) { - for (const match of matches) { - let explode = false; - let name = match.substring(1, match.length - 1); - let style: ArraySeparatorStyle = 'simple'; + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = "simple"; - if (name.endsWith('*')) { - explode = true; - name = name.substring(0, name.length - 1); - } + if (name.endsWith("*")) { + explode = true; + name = name.substring(0, name.length - 1); + } - if (name.startsWith('.')) { - name = name.substring(1); - style = 'label'; - } else if (name.startsWith(';')) { - name = name.substring(1); - style = 'matrix'; - } + if (name.startsWith(".")) { + name = name.substring(1); + style = "label"; + } else if (name.startsWith(";")) { + name = name.substring(1); + style = "matrix"; + } - const value = path[name]; + const value = path[name]; - if (value === undefined || value === null) { - continue; - } + if (value === undefined || value === null) { + continue; + } - if (Array.isArray(value)) { - url = url.replace( - match, - serializeArrayParam({ explode, name, style, value }), - ); - continue; - } + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } - if (typeof value === 'object') { - url = url.replace( - match, - serializeObjectParam({ - explode, - name, - style, - value: value as Record, - valueOnly: true, - }), - ); - continue; - } + if (typeof value === "object") { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } - if (style === 'matrix') { - url = url.replace( - match, - `;${serializePrimitiveParam({ - name, - value: value as string, - })}`, - ); - continue; - } + if (style === "matrix") { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } - const replaceValue = encodeURIComponent( - style === 'label' ? `.${value as string}` : (value as string), - ); - url = url.replace(match, replaceValue); - } - } - return url; + const replaceValue = encodeURIComponent( + style === "label" ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; }; export const getUrl = ({ - baseUrl, - path, - query, - querySerializer, - url: _url, + baseUrl, + path, + query, + querySerializer, + url: _url, }: { - baseUrl?: string; - path?: Record; - query?: Record; - querySerializer: QuerySerializer; - url: string; + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; }) => { - const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; - let url = (baseUrl ?? '') + pathUrl; - if (path) { - url = defaultPathSerializer({ path, url }); - } - let search = query ? querySerializer(query) : ''; - if (search.startsWith('?')) { - search = search.substring(1); - } - if (search) { - url += `?${search}`; - } - return url; + const pathUrl = _url.startsWith("/") ? _url : `/${_url}`; + let url = (baseUrl ?? "") + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ""; + if (search.startsWith("?")) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; }; export function getValidRequestBody(options: { - body?: unknown; - bodySerializer?: BodySerializer | null; - serializedBody?: unknown; + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; }) { - const hasBody = options.body !== undefined; - const isSerializedBody = hasBody && options.bodySerializer; + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; - if (isSerializedBody) { - if ('serializedBody' in options) { - const hasSerializedBody = - options.serializedBody !== undefined && options.serializedBody !== ''; + if (isSerializedBody) { + if ("serializedBody" in options) { + const hasSerializedBody = + options.serializedBody !== undefined && options.serializedBody !== ""; - return hasSerializedBody ? options.serializedBody : null; - } + return hasSerializedBody ? options.serializedBody : null; + } - // not all clients implement a serializedBody property (i.e. client-axios) - return options.body !== '' ? options.body : null; - } + // not all clients implement a serializedBody property (i.e. client-axios) + return options.body !== "" ? options.body : null; + } - // plain/text body - if (hasBody) { - return options.body; - } + // plain/text body + if (hasBody) { + return options.body; + } - // no body was provided - return undefined; + // no body was provided + return undefined; } diff --git a/app/client/api-client/index.ts b/app/client/api-client/index.ts index c352c104..7a716924 100644 --- a/app/client/api-client/index.ts +++ b/app/client/api-client/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export type * from './types.gen'; -export * from './sdk.gen'; +export type * from "./types.gen"; +export * from "./sdk.gen"; diff --git a/app/client/api-client/sdk.gen.ts b/app/client/api-client/sdk.gen.ts index 6760a83b..5d28a09c 100644 --- a/app/client/api-client/sdk.gen.ts +++ b/app/client/api-client/sdk.gen.ts @@ -1,427 +1,849 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Client, Options as Options2, TDataShape } from './client'; -import { client } from './client.gen'; -import type { BrowseFilesystemData, BrowseFilesystemResponses, ChangePasswordData, ChangePasswordResponses, CreateBackupScheduleData, CreateBackupScheduleResponses, CreateNotificationDestinationData, CreateNotificationDestinationResponses, CreateRepositoryData, CreateRepositoryResponses, CreateVolumeData, CreateVolumeResponses, DeleteBackupScheduleData, DeleteBackupScheduleResponses, DeleteNotificationDestinationData, DeleteNotificationDestinationErrors, DeleteNotificationDestinationResponses, DeleteRepositoryData, DeleteRepositoryResponses, DeleteSnapshotData, DeleteSnapshotResponses, DeleteSnapshotsData, DeleteSnapshotsResponses, DeleteVolumeData, DeleteVolumeResponses, DoctorRepositoryData, DoctorRepositoryResponses, DownloadResticPasswordData, DownloadResticPasswordResponses, GetBackupScheduleData, GetBackupScheduleForVolumeData, GetBackupScheduleForVolumeResponses, GetBackupScheduleResponses, GetMeData, GetMeResponses, GetMirrorCompatibilityData, GetMirrorCompatibilityResponses, GetNotificationDestinationData, GetNotificationDestinationErrors, GetNotificationDestinationResponses, GetRepositoryData, GetRepositoryResponses, GetScheduleMirrorsData, GetScheduleMirrorsResponses, GetScheduleNotificationsData, GetScheduleNotificationsResponses, GetSnapshotDetailsData, GetSnapshotDetailsResponses, GetStatusData, GetStatusResponses, GetSystemInfoData, GetSystemInfoResponses, GetUpdatesData, GetUpdatesResponses, GetVolumeData, GetVolumeErrors, GetVolumeResponses, HealthCheckVolumeData, HealthCheckVolumeErrors, HealthCheckVolumeResponses, ListBackupSchedulesData, ListBackupSchedulesResponses, ListFilesData, ListFilesResponses, ListNotificationDestinationsData, ListNotificationDestinationsResponses, ListRcloneRemotesData, ListRcloneRemotesResponses, ListRepositoriesData, ListRepositoriesResponses, ListSnapshotFilesData, ListSnapshotFilesResponses, ListSnapshotsData, ListSnapshotsResponses, ListVolumesData, ListVolumesResponses, LoginData, LoginResponses, LogoutData, LogoutResponses, MountVolumeData, MountVolumeResponses, RegisterData, RegisterResponses, ReorderBackupSchedulesData, ReorderBackupSchedulesResponses, RestoreSnapshotData, RestoreSnapshotResponses, RunBackupNowData, RunBackupNowResponses, RunForgetData, RunForgetResponses, StopBackupData, StopBackupErrors, StopBackupResponses, TagSnapshotsData, TagSnapshotsResponses, TestConnectionData, TestConnectionResponses, TestNotificationDestinationData, TestNotificationDestinationErrors, TestNotificationDestinationResponses, UnmountVolumeData, UnmountVolumeResponses, UpdateBackupScheduleData, UpdateBackupScheduleResponses, UpdateNotificationDestinationData, UpdateNotificationDestinationErrors, UpdateNotificationDestinationResponses, UpdateRepositoryData, UpdateRepositoryErrors, UpdateRepositoryResponses, UpdateScheduleMirrorsData, UpdateScheduleMirrorsResponses, UpdateScheduleNotificationsData, UpdateScheduleNotificationsResponses, UpdateVolumeData, UpdateVolumeErrors, UpdateVolumeResponses } from './types.gen'; +import type { Client, Options as Options2, TDataShape } from "./client"; +import { client } from "./client.gen"; +import type { + BrowseFilesystemData, + BrowseFilesystemResponses, + CreateBackupScheduleData, + CreateBackupScheduleResponses, + CreateNotificationDestinationData, + CreateNotificationDestinationResponses, + CreateRepositoryData, + CreateRepositoryResponses, + CreateVolumeData, + CreateVolumeResponses, + DeleteBackupScheduleData, + DeleteBackupScheduleResponses, + DeleteNotificationDestinationData, + DeleteNotificationDestinationErrors, + DeleteNotificationDestinationResponses, + DeleteRepositoryData, + DeleteRepositoryResponses, + DeleteSnapshotData, + DeleteSnapshotResponses, + DeleteSnapshotsData, + DeleteSnapshotsResponses, + DeleteVolumeData, + DeleteVolumeResponses, + DoctorRepositoryData, + DoctorRepositoryResponses, + DownloadResticPasswordData, + DownloadResticPasswordResponses, + GetBackupScheduleData, + GetBackupScheduleForVolumeData, + GetBackupScheduleForVolumeResponses, + GetBackupScheduleResponses, + GetMirrorCompatibilityData, + GetMirrorCompatibilityResponses, + GetNotificationDestinationData, + GetNotificationDestinationErrors, + GetNotificationDestinationResponses, + GetRepositoryData, + GetRepositoryResponses, + GetScheduleMirrorsData, + GetScheduleMirrorsResponses, + GetScheduleNotificationsData, + GetScheduleNotificationsResponses, + GetSnapshotDetailsData, + GetSnapshotDetailsResponses, + GetStatusData, + GetStatusResponses, + GetSystemInfoData, + GetSystemInfoResponses, + GetUpdatesData, + GetUpdatesResponses, + GetVolumeData, + GetVolumeErrors, + GetVolumeResponses, + HealthCheckVolumeData, + HealthCheckVolumeErrors, + HealthCheckVolumeResponses, + ListBackupSchedulesData, + ListBackupSchedulesResponses, + ListFilesData, + ListFilesResponses, + ListNotificationDestinationsData, + ListNotificationDestinationsResponses, + ListRcloneRemotesData, + ListRcloneRemotesResponses, + ListRepositoriesData, + ListRepositoriesResponses, + ListSnapshotFilesData, + ListSnapshotFilesResponses, + ListSnapshotsData, + ListSnapshotsResponses, + ListVolumesData, + ListVolumesResponses, + MountVolumeData, + MountVolumeResponses, + ReorderBackupSchedulesData, + ReorderBackupSchedulesResponses, + RestoreSnapshotData, + RestoreSnapshotResponses, + RunBackupNowData, + RunBackupNowResponses, + RunForgetData, + RunForgetResponses, + StopBackupData, + StopBackupErrors, + StopBackupResponses, + TagSnapshotsData, + TagSnapshotsResponses, + TestConnectionData, + TestConnectionResponses, + TestNotificationDestinationData, + TestNotificationDestinationErrors, + TestNotificationDestinationResponses, + UnmountVolumeData, + UnmountVolumeResponses, + UpdateBackupScheduleData, + UpdateBackupScheduleResponses, + UpdateNotificationDestinationData, + UpdateNotificationDestinationErrors, + UpdateNotificationDestinationResponses, + UpdateRepositoryData, + UpdateRepositoryErrors, + UpdateRepositoryResponses, + UpdateScheduleMirrorsData, + UpdateScheduleMirrorsResponses, + UpdateScheduleNotificationsData, + UpdateScheduleNotificationsResponses, + UpdateVolumeData, + UpdateVolumeErrors, + UpdateVolumeResponses, +} from "./types.gen"; -export type Options = Options2 & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; - /** - * You can pass arbitrary values through the `meta` object. This can be - * used to access values that aren't defined as part of the SDK function. - */ - meta?: Record; +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, +> = Options2 & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; }; -/** - * Register a new user - */ -export const register = (options?: Options) => (options?.client ?? client).post({ - url: '/api/v1/auth/register', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } -}); - -/** - * Login with username and password - */ -export const login = (options?: Options) => (options?.client ?? client).post({ - url: '/api/v1/auth/login', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } -}); - -/** - * Logout current user - */ -export const logout = (options?: Options) => (options?.client ?? client).post({ url: '/api/v1/auth/logout', ...options }); - -/** - * Get current authenticated user - */ -export const getMe = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/auth/me', ...options }); - /** * Get authentication system status */ -export const getStatus = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/auth/status', ...options }); - -/** - * Change current user password - */ -export const changePassword = (options?: Options) => (options?.client ?? client).post({ - url: '/api/v1/auth/change-password', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } -}); +export const getStatus = ( + options?: Options, +) => + (options?.client ?? client).get({ + url: "/api/v1/auth/status", + ...options, + }); /** * List all volumes */ -export const listVolumes = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/volumes', ...options }); +export const listVolumes = ( + options?: Options, +) => + (options?.client ?? client).get({ + url: "/api/v1/volumes", + ...options, + }); /** * Create a new volume */ -export const createVolume = (options?: Options) => (options?.client ?? client).post({ - url: '/api/v1/volumes', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } -}); +export const createVolume = ( + options?: Options, +) => + (options?.client ?? client).post< + CreateVolumeResponses, + unknown, + ThrowOnError + >({ + url: "/api/v1/volumes", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); /** * Test connection to backend */ -export const testConnection = (options?: Options) => (options?.client ?? client).post({ - url: '/api/v1/volumes/test-connection', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } -}); +export const testConnection = ( + options?: Options, +) => + (options?.client ?? client).post< + TestConnectionResponses, + unknown, + ThrowOnError + >({ + url: "/api/v1/volumes/test-connection", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); /** * Delete a volume */ -export const deleteVolume = (options: Options) => (options.client ?? client).delete({ url: '/api/v1/volumes/{name}', ...options }); +export const deleteVolume = ( + options: Options, +) => + (options.client ?? client).delete< + DeleteVolumeResponses, + unknown, + ThrowOnError + >({ url: "/api/v1/volumes/{name}", ...options }); /** * Get a volume by name */ -export const getVolume = (options: Options) => (options.client ?? client).get({ url: '/api/v1/volumes/{name}', ...options }); +export const getVolume = ( + options: Options, +) => + (options.client ?? client).get< + GetVolumeResponses, + GetVolumeErrors, + ThrowOnError + >({ url: "/api/v1/volumes/{name}", ...options }); /** * Update a volume's configuration */ -export const updateVolume = (options: Options) => (options.client ?? client).put({ - url: '/api/v1/volumes/{name}', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); +export const updateVolume = ( + options: Options, +) => + (options.client ?? client).put< + UpdateVolumeResponses, + UpdateVolumeErrors, + ThrowOnError + >({ + url: "/api/v1/volumes/{name}", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }); /** * Mount a volume */ -export const mountVolume = (options: Options) => (options.client ?? client).post({ url: '/api/v1/volumes/{name}/mount', ...options }); +export const mountVolume = ( + options: Options, +) => + (options.client ?? client).post({ + url: "/api/v1/volumes/{name}/mount", + ...options, + }); /** * Unmount a volume */ -export const unmountVolume = (options: Options) => (options.client ?? client).post({ url: '/api/v1/volumes/{name}/unmount', ...options }); +export const unmountVolume = ( + options: Options, +) => + (options.client ?? client).post< + UnmountVolumeResponses, + unknown, + ThrowOnError + >({ url: "/api/v1/volumes/{name}/unmount", ...options }); /** * Perform a health check on a volume */ -export const healthCheckVolume = (options: Options) => (options.client ?? client).post({ url: '/api/v1/volumes/{name}/health-check', ...options }); +export const healthCheckVolume = ( + options: Options, +) => + (options.client ?? client).post< + HealthCheckVolumeResponses, + HealthCheckVolumeErrors, + ThrowOnError + >({ url: "/api/v1/volumes/{name}/health-check", ...options }); /** * List files in a volume directory */ -export const listFiles = (options: Options) => (options.client ?? client).get({ url: '/api/v1/volumes/{name}/files', ...options }); +export const listFiles = ( + options: Options, +) => + (options.client ?? client).get({ + url: "/api/v1/volumes/{name}/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, +) => + (options?.client ?? client).get< + BrowseFilesystemResponses, + unknown, + ThrowOnError + >({ 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, +) => + (options?.client ?? client).get< + ListRepositoriesResponses, + unknown, + ThrowOnError + >({ url: "/api/v1/repositories", ...options }); /** * Create a new restic repository */ -export const createRepository = (options?: Options) => (options?.client ?? client).post({ - url: '/api/v1/repositories', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } -}); +export const createRepository = ( + options?: Options, +) => + (options?.client ?? client).post< + CreateRepositoryResponses, + unknown, + ThrowOnError + >({ + url: "/api/v1/repositories", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); /** * 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, +) => + (options?.client ?? client).get< + ListRcloneRemotesResponses, + unknown, + ThrowOnError + >({ url: "/api/v1/repositories/rclone-remotes", ...options }); /** * Delete a repository */ -export const deleteRepository = (options: Options) => (options.client ?? client).delete({ url: '/api/v1/repositories/{id}', ...options }); +export const deleteRepository = ( + options: Options, +) => + (options.client ?? client).delete< + DeleteRepositoryResponses, + unknown, + ThrowOnError + >({ url: "/api/v1/repositories/{id}", ...options }); /** * Get a single repository by ID */ -export const getRepository = (options: Options) => (options.client ?? client).get({ url: '/api/v1/repositories/{id}', ...options }); +export const getRepository = ( + options: Options, +) => + (options.client ?? client).get( + { url: "/api/v1/repositories/{id}", ...options }, + ); /** * Update a repository's name or settings */ -export const updateRepository = (options: Options) => (options.client ?? client).patch({ - url: '/api/v1/repositories/{id}', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); +export const updateRepository = ( + options: Options, +) => + (options.client ?? client).patch< + UpdateRepositoryResponses, + UpdateRepositoryErrors, + ThrowOnError + >({ + url: "/api/v1/repositories/{id}", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }); /** * Delete multiple snapshots from a repository */ -export const deleteSnapshots = (options: Options) => (options.client ?? client).delete({ - url: '/api/v1/repositories/{id}/snapshots', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); +export const deleteSnapshots = ( + options: Options, +) => + (options.client ?? client).delete< + DeleteSnapshotsResponses, + unknown, + ThrowOnError + >({ + url: "/api/v1/repositories/{id}/snapshots", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }); /** * List all snapshots in a repository */ -export const listSnapshots = (options: Options) => (options.client ?? client).get({ url: '/api/v1/repositories/{id}/snapshots', ...options }); +export const listSnapshots = ( + options: Options, +) => + (options.client ?? client).get( + { url: "/api/v1/repositories/{id}/snapshots", ...options }, + ); /** * Delete a specific snapshot from a repository */ -export const deleteSnapshot = (options: Options) => (options.client ?? client).delete({ url: '/api/v1/repositories/{id}/snapshots/{snapshotId}', ...options }); +export const deleteSnapshot = ( + options: Options, +) => + (options.client ?? client).delete< + DeleteSnapshotResponses, + unknown, + ThrowOnError + >({ url: "/api/v1/repositories/{id}/snapshots/{snapshotId}", ...options }); /** * Get details of a specific snapshot */ -export const getSnapshotDetails = (options: Options) => (options.client ?? client).get({ url: '/api/v1/repositories/{id}/snapshots/{snapshotId}', ...options }); +export const getSnapshotDetails = ( + options: Options, +) => + (options.client ?? client).get< + GetSnapshotDetailsResponses, + unknown, + ThrowOnError + >({ url: "/api/v1/repositories/{id}/snapshots/{snapshotId}", ...options }); /** * List files and directories in a snapshot */ -export const listSnapshotFiles = (options: Options) => (options.client ?? client).get({ url: '/api/v1/repositories/{id}/snapshots/{snapshotId}/files', ...options }); +export const listSnapshotFiles = ( + options: Options, +) => + (options.client ?? client).get< + ListSnapshotFilesResponses, + unknown, + ThrowOnError + >({ + url: "/api/v1/repositories/{id}/snapshots/{snapshotId}/files", + ...options, + }); /** * Restore a snapshot to a target path on the filesystem */ -export const restoreSnapshot = (options: Options) => (options.client ?? client).post({ - url: '/api/v1/repositories/{id}/restore', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); +export const restoreSnapshot = ( + options: Options, +) => + (options.client ?? client).post< + RestoreSnapshotResponses, + unknown, + ThrowOnError + >({ + url: "/api/v1/repositories/{id}/restore", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }); /** * Run doctor operations on a repository to fix common issues (unlock, check, repair index). Use this when the repository is locked or has errors. */ -export const doctorRepository = (options: Options) => (options.client ?? client).post({ url: '/api/v1/repositories/{id}/doctor', ...options }); +export const doctorRepository = ( + options: Options, +) => + (options.client ?? client).post< + DoctorRepositoryResponses, + unknown, + ThrowOnError + >({ url: "/api/v1/repositories/{id}/doctor", ...options }); /** * Tag multiple snapshots in a repository */ -export const tagSnapshots = (options: Options) => (options.client ?? client).post({ - url: '/api/v1/repositories/{id}/snapshots/tag', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); +export const tagSnapshots = ( + options: Options, +) => + (options.client ?? client).post( + { + url: "/api/v1/repositories/{id}/snapshots/tag", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }, + ); /** * List all backup schedules */ -export const listBackupSchedules = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/backups', ...options }); +export const listBackupSchedules = ( + options?: Options, +) => + (options?.client ?? client).get< + ListBackupSchedulesResponses, + unknown, + ThrowOnError + >({ url: "/api/v1/backups", ...options }); /** * Create a new backup schedule for a volume */ -export const createBackupSchedule = (options?: Options) => (options?.client ?? client).post({ - url: '/api/v1/backups', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } -}); +export const createBackupSchedule = ( + options?: Options, +) => + (options?.client ?? client).post< + CreateBackupScheduleResponses, + unknown, + ThrowOnError + >({ + url: "/api/v1/backups", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); /** * Delete a backup schedule */ -export const deleteBackupSchedule = (options: Options) => (options.client ?? client).delete({ url: '/api/v1/backups/{scheduleId}', ...options }); +export const deleteBackupSchedule = ( + options: Options, +) => + (options.client ?? client).delete< + DeleteBackupScheduleResponses, + unknown, + ThrowOnError + >({ url: "/api/v1/backups/{scheduleId}", ...options }); /** * Get a backup schedule by ID */ -export const getBackupSchedule = (options: Options) => (options.client ?? client).get({ url: '/api/v1/backups/{scheduleId}', ...options }); +export const getBackupSchedule = ( + options: Options, +) => + (options.client ?? client).get< + GetBackupScheduleResponses, + unknown, + ThrowOnError + >({ url: "/api/v1/backups/{scheduleId}", ...options }); /** * Update a backup schedule */ -export const updateBackupSchedule = (options: Options) => (options.client ?? client).patch({ - url: '/api/v1/backups/{scheduleId}', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); +export const updateBackupSchedule = ( + options: Options, +) => + (options.client ?? client).patch< + UpdateBackupScheduleResponses, + unknown, + ThrowOnError + >({ + url: "/api/v1/backups/{scheduleId}", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }); /** * Get a backup schedule for a specific volume */ -export const getBackupScheduleForVolume = (options: Options) => (options.client ?? client).get({ url: '/api/v1/backups/volume/{volumeId}', ...options }); +export const getBackupScheduleForVolume = < + ThrowOnError extends boolean = false, +>( + options: Options, +) => + (options.client ?? client).get< + GetBackupScheduleForVolumeResponses, + unknown, + ThrowOnError + >({ url: "/api/v1/backups/volume/{volumeId}", ...options }); /** * Trigger a backup immediately for a schedule */ -export const runBackupNow = (options: Options) => (options.client ?? client).post({ url: '/api/v1/backups/{scheduleId}/run', ...options }); +export const runBackupNow = ( + options: Options, +) => + (options.client ?? client).post( + { url: "/api/v1/backups/{scheduleId}/run", ...options }, + ); /** * Stop a backup that is currently in progress */ -export const stopBackup = (options: Options) => (options.client ?? client).post({ url: '/api/v1/backups/{scheduleId}/stop', ...options }); +export const stopBackup = ( + options: Options, +) => + (options.client ?? client).post< + StopBackupResponses, + StopBackupErrors, + ThrowOnError + >({ url: "/api/v1/backups/{scheduleId}/stop", ...options }); /** * Manually apply retention policy to clean up old snapshots */ -export const runForget = (options: Options) => (options.client ?? client).post({ url: '/api/v1/backups/{scheduleId}/forget', ...options }); +export const runForget = ( + options: Options, +) => + (options.client ?? client).post({ + url: "/api/v1/backups/{scheduleId}/forget", + ...options, + }); /** * Get notification assignments for a backup schedule */ -export const getScheduleNotifications = (options: Options) => (options.client ?? client).get({ url: '/api/v1/backups/{scheduleId}/notifications', ...options }); +export const getScheduleNotifications = ( + options: Options, +) => + (options.client ?? client).get< + GetScheduleNotificationsResponses, + unknown, + ThrowOnError + >({ url: "/api/v1/backups/{scheduleId}/notifications", ...options }); /** * Update notification assignments for a backup schedule */ -export const updateScheduleNotifications = (options: Options) => (options.client ?? client).put({ - url: '/api/v1/backups/{scheduleId}/notifications', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); +export const updateScheduleNotifications = < + ThrowOnError extends boolean = false, +>( + options: Options, +) => + (options.client ?? client).put< + UpdateScheduleNotificationsResponses, + unknown, + ThrowOnError + >({ + url: "/api/v1/backups/{scheduleId}/notifications", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }); /** * Get mirror repository assignments for a backup schedule */ -export const getScheduleMirrors = (options: Options) => (options.client ?? client).get({ url: '/api/v1/backups/{scheduleId}/mirrors', ...options }); +export const getScheduleMirrors = ( + options: Options, +) => + (options.client ?? client).get< + GetScheduleMirrorsResponses, + unknown, + ThrowOnError + >({ url: "/api/v1/backups/{scheduleId}/mirrors", ...options }); /** * Update mirror repository assignments for a backup schedule */ -export const updateScheduleMirrors = (options: Options) => (options.client ?? client).put({ - url: '/api/v1/backups/{scheduleId}/mirrors', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); +export const updateScheduleMirrors = ( + options: Options, +) => + (options.client ?? client).put< + UpdateScheduleMirrorsResponses, + unknown, + ThrowOnError + >({ + url: "/api/v1/backups/{scheduleId}/mirrors", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }); /** * 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/{scheduleId}/mirrors/compatibility', ...options }); +export const getMirrorCompatibility = ( + options: Options, +) => + (options.client ?? client).get< + GetMirrorCompatibilityResponses, + unknown, + ThrowOnError + >({ url: "/api/v1/backups/{scheduleId}/mirrors/compatibility", ...options }); /** * Reorder backup schedules by providing an array of schedule IDs in the desired order */ -export const reorderBackupSchedules = (options?: Options) => (options?.client ?? client).post({ - url: '/api/v1/backups/reorder', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } -}); +export const reorderBackupSchedules = ( + options?: Options, +) => + (options?.client ?? client).post< + ReorderBackupSchedulesResponses, + unknown, + ThrowOnError + >({ + url: "/api/v1/backups/reorder", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); /** * List all notification destinations */ -export const listNotificationDestinations = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/notifications/destinations', ...options }); +export const listNotificationDestinations = < + ThrowOnError extends boolean = false, +>( + options?: Options, +) => + (options?.client ?? client).get< + ListNotificationDestinationsResponses, + unknown, + ThrowOnError + >({ url: "/api/v1/notifications/destinations", ...options }); /** * Create a new notification destination */ -export const createNotificationDestination = (options?: Options) => (options?.client ?? client).post({ - url: '/api/v1/notifications/destinations', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } -}); +export const createNotificationDestination = < + ThrowOnError extends boolean = false, +>( + options?: Options, +) => + (options?.client ?? client).post< + CreateNotificationDestinationResponses, + unknown, + ThrowOnError + >({ + url: "/api/v1/notifications/destinations", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); /** * Delete a notification destination */ -export const deleteNotificationDestination = (options: Options) => (options.client ?? client).delete({ url: '/api/v1/notifications/destinations/{id}', ...options }); +export const deleteNotificationDestination = < + ThrowOnError extends boolean = false, +>( + options: Options, +) => + (options.client ?? client).delete< + DeleteNotificationDestinationResponses, + DeleteNotificationDestinationErrors, + ThrowOnError + >({ 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 = < + ThrowOnError extends boolean = false, +>( + options: Options, +) => + (options.client ?? client).get< + GetNotificationDestinationResponses, + GetNotificationDestinationErrors, + ThrowOnError + >({ url: "/api/v1/notifications/destinations/{id}", ...options }); /** * Update a notification destination */ -export const updateNotificationDestination = (options: Options) => (options.client ?? client).patch({ - url: '/api/v1/notifications/destinations/{id}', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } -}); +export const updateNotificationDestination = < + ThrowOnError extends boolean = false, +>( + options: Options, +) => + (options.client ?? client).patch< + UpdateNotificationDestinationResponses, + UpdateNotificationDestinationErrors, + ThrowOnError + >({ + url: "/api/v1/notifications/destinations/{id}", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }); /** * Test a notification destination by sending a test message */ -export const testNotificationDestination = (options: Options) => (options.client ?? client).post({ url: '/api/v1/notifications/destinations/{id}/test', ...options }); +export const testNotificationDestination = < + ThrowOnError extends boolean = false, +>( + options: Options, +) => + (options.client ?? client).post< + TestNotificationDestinationResponses, + TestNotificationDestinationErrors, + ThrowOnError + >({ 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, +) => + (options?.client ?? client).get< + GetSystemInfoResponses, + unknown, + ThrowOnError + >({ 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, +) => + (options?.client ?? client).get({ + url: "/api/v1/system/updates", + ...options, + }); /** * Download the Restic password file for backup recovery. Requires password re-authentication. */ -export const downloadResticPassword = (options?: Options) => (options?.client ?? client).post({ - url: '/api/v1/system/restic-password', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } -}); +export const downloadResticPassword = ( + options?: Options, +) => + (options?.client ?? client).post< + DownloadResticPasswordResponses, + unknown, + ThrowOnError + >({ + url: "/api/v1/system/restic-password", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }); diff --git a/app/client/api-client/types.gen.ts b/app/client/api-client/types.gen.ts index 5c8f13e5..7230304f 100644 --- a/app/client/api-client/types.gen.ts +++ b/app/client/api-client/types.gen.ts @@ -1,3339 +1,3563 @@ // This file is auto-generated by @hey-api/openapi-ts export type ClientOptions = { - baseUrl: 'http://localhost:4096' | (string & {}); + baseUrl: "http://localhost:4096" | (string & {}); }; -export type RegisterData = { - body?: { - password: string; - username: string; - }; - path?: never; - query?: never; - url: '/api/v1/auth/register'; -}; - -export type RegisterResponses = { - /** - * User created successfully - */ - 201: { - message: string; - success: boolean; - user?: { - hasDownloadedResticPassword: boolean; - id: number; - username: string; - }; - }; -}; - -export type RegisterResponse = RegisterResponses[keyof RegisterResponses]; - -export type LoginData = { - body?: { - password: string; - username: string; - }; - path?: never; - query?: never; - url: '/api/v1/auth/login'; -}; - -export type LoginResponses = { - /** - * Login successful - */ - 200: { - message: string; - success: boolean; - user?: { - hasDownloadedResticPassword: boolean; - id: number; - username: string; - }; - }; -}; - -export type LoginResponse = LoginResponses[keyof LoginResponses]; - -export type LogoutData = { - body?: never; - path?: never; - query?: never; - url: '/api/v1/auth/logout'; -}; - -export type LogoutResponses = { - /** - * Logout successful - */ - 200: { - success: boolean; - }; -}; - -export type LogoutResponse = LogoutResponses[keyof LogoutResponses]; - -export type GetMeData = { - body?: never; - path?: never; - query?: never; - url: '/api/v1/auth/me'; -}; - -export type GetMeResponses = { - /** - * Current user information - */ - 200: { - message: string; - success: boolean; - user?: { - hasDownloadedResticPassword: boolean; - id: number; - username: string; - }; - }; -}; - -export type GetMeResponse = GetMeResponses[keyof GetMeResponses]; - export type GetStatusData = { - body?: never; - path?: never; - query?: never; - url: '/api/v1/auth/status'; + body?: never; + path?: never; + query?: never; + url: "/api/v1/auth/status"; }; export type GetStatusResponses = { - /** - * Authentication system status - */ - 200: { - hasUsers: boolean; - }; + /** + * Authentication system status + */ + 200: { + hasUsers: boolean; + }; }; export type GetStatusResponse = GetStatusResponses[keyof GetStatusResponses]; -export type ChangePasswordData = { - body?: { - currentPassword: string; - newPassword: string; - }; - path?: never; - query?: never; - url: '/api/v1/auth/change-password'; -}; - -export type ChangePasswordResponses = { - /** - * Password changed successfully - */ - 200: { - message: string; - success: boolean; - }; -}; - -export type ChangePasswordResponse = ChangePasswordResponses[keyof ChangePasswordResponses]; - export type ListVolumesData = { - body?: never; - path?: never; - query?: never; - url: '/api/v1/volumes'; + body?: never; + path?: never; + query?: never; + url: "/api/v1/volumes"; }; export type ListVolumesResponses = { - /** - * A list of volumes - */ - 200: Array<{ - autoRemount: boolean; - config: { - backend: 'directory'; - path: string; - readOnly?: false; - } | { - backend: 'nfs'; - exportPath: string; - server: string; - version: '3' | '4' | '4.1'; - port?: number; - readOnly?: boolean; - } | { - backend: 'rclone'; - path: string; - remote: string; - readOnly?: boolean; - } | { - backend: 'sftp'; - host: string; - path: string; - username: string; - port?: number; - skipHostKeyCheck?: boolean; - knownHosts?: string; - password?: string; - privateKey?: string; - readOnly?: boolean; - } | { - backend: 'smb'; - password: string; - server: string; - share: string; - username: string; - vers?: '1.0' | '2.0' | '2.1' | '3.0'; - port?: number; - domain?: string; - readOnly?: boolean; - } | { - backend: 'webdav'; - path: string; - server: string; - port?: number; - password?: string; - readOnly?: boolean; - ssl?: boolean; - username?: string; - }; - createdAt: number; - id: number; - lastError: string | null; - lastHealthCheck: number; - name: string; - shortId: string; - status: 'error' | 'mounted' | 'unmounted'; - type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav'; - updatedAt: number; - }>; + /** + * A list of volumes + */ + 200: Array<{ + autoRemount: boolean; + config: + | { + backend: "directory"; + path: string; + readOnly?: false; + } + | { + backend: "nfs"; + exportPath: string; + server: string; + version: "3" | "4" | "4.1"; + port?: number; + readOnly?: boolean; + } + | { + backend: "rclone"; + path: string; + remote: string; + readOnly?: boolean; + } + | { + backend: "sftp"; + host: string; + path: string; + username: string; + port?: number; + skipHostKeyCheck?: boolean; + knownHosts?: string; + password?: string; + privateKey?: string; + readOnly?: boolean; + } + | { + backend: "smb"; + password: string; + server: string; + share: string; + username: string; + vers?: "1.0" | "2.0" | "2.1" | "3.0"; + port?: number; + domain?: string; + readOnly?: boolean; + } + | { + backend: "webdav"; + path: string; + server: string; + port?: number; + password?: string; + readOnly?: boolean; + ssl?: boolean; + username?: string; + }; + createdAt: number; + id: number; + lastError: string | null; + lastHealthCheck: number; + name: string; + shortId: string; + status: "error" | "mounted" | "unmounted"; + type: "directory" | "nfs" | "rclone" | "sftp" | "smb" | "webdav"; + updatedAt: number; + }>; }; -export type ListVolumesResponse = ListVolumesResponses[keyof ListVolumesResponses]; +export type ListVolumesResponse = + ListVolumesResponses[keyof ListVolumesResponses]; export type CreateVolumeData = { - body?: { - config: { - backend: 'directory'; - path: string; - readOnly?: false; - } | { - backend: 'nfs'; - exportPath: string; - server: string; - version: '3' | '4' | '4.1'; - port?: number; - readOnly?: boolean; - } | { - backend: 'rclone'; - path: string; - remote: string; - readOnly?: boolean; - } | { - backend: 'sftp'; - host: string; - path: string; - username: string; - port?: number; - skipHostKeyCheck?: boolean; - knownHosts?: string; - password?: string; - privateKey?: string; - readOnly?: boolean; - } | { - backend: 'smb'; - password: string; - server: string; - share: string; - username: string; - vers?: '1.0' | '2.0' | '2.1' | '3.0'; - port?: number; - domain?: string; - readOnly?: boolean; - } | { - backend: 'webdav'; - path: string; - server: string; - port?: number; - password?: string; - readOnly?: boolean; - ssl?: boolean; - username?: string; - }; - name: string; - }; - path?: never; - query?: never; - url: '/api/v1/volumes'; + body?: { + config: + | { + backend: "directory"; + path: string; + readOnly?: false; + } + | { + backend: "nfs"; + exportPath: string; + server: string; + version: "3" | "4" | "4.1"; + port?: number; + readOnly?: boolean; + } + | { + backend: "rclone"; + path: string; + remote: string; + readOnly?: boolean; + } + | { + backend: "sftp"; + host: string; + path: string; + username: string; + port?: number; + skipHostKeyCheck?: boolean; + knownHosts?: string; + password?: string; + privateKey?: string; + readOnly?: boolean; + } + | { + backend: "smb"; + password: string; + server: string; + share: string; + username: string; + vers?: "1.0" | "2.0" | "2.1" | "3.0"; + port?: number; + domain?: string; + readOnly?: boolean; + } + | { + backend: "webdav"; + path: string; + server: string; + port?: number; + password?: string; + readOnly?: boolean; + ssl?: boolean; + username?: string; + }; + name: string; + }; + path?: never; + query?: never; + url: "/api/v1/volumes"; }; export type CreateVolumeResponses = { - /** - * Volume created successfully - */ - 201: { - autoRemount: boolean; - config: { - backend: 'directory'; - path: string; - readOnly?: false; - } | { - backend: 'nfs'; - exportPath: string; - server: string; - version: '3' | '4' | '4.1'; - port?: number; - readOnly?: boolean; - } | { - backend: 'rclone'; - path: string; - remote: string; - readOnly?: boolean; - } | { - backend: 'sftp'; - host: string; - path: string; - username: string; - port?: number; - skipHostKeyCheck?: boolean; - knownHosts?: string; - password?: string; - privateKey?: string; - readOnly?: boolean; - } | { - backend: 'smb'; - password: string; - server: string; - share: string; - username: string; - vers?: '1.0' | '2.0' | '2.1' | '3.0'; - port?: number; - domain?: string; - readOnly?: boolean; - } | { - backend: 'webdav'; - path: string; - server: string; - port?: number; - password?: string; - readOnly?: boolean; - ssl?: boolean; - username?: string; - }; - createdAt: number; - id: number; - lastError: string | null; - lastHealthCheck: number; - name: string; - shortId: string; - status: 'error' | 'mounted' | 'unmounted'; - type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav'; - updatedAt: number; - }; + /** + * Volume created successfully + */ + 201: { + autoRemount: boolean; + config: + | { + backend: "directory"; + path: string; + readOnly?: false; + } + | { + backend: "nfs"; + exportPath: string; + server: string; + version: "3" | "4" | "4.1"; + port?: number; + readOnly?: boolean; + } + | { + backend: "rclone"; + path: string; + remote: string; + readOnly?: boolean; + } + | { + backend: "sftp"; + host: string; + path: string; + username: string; + port?: number; + skipHostKeyCheck?: boolean; + knownHosts?: string; + password?: string; + privateKey?: string; + readOnly?: boolean; + } + | { + backend: "smb"; + password: string; + server: string; + share: string; + username: string; + vers?: "1.0" | "2.0" | "2.1" | "3.0"; + port?: number; + domain?: string; + readOnly?: boolean; + } + | { + backend: "webdav"; + path: string; + server: string; + port?: number; + password?: string; + readOnly?: boolean; + ssl?: boolean; + username?: string; + }; + createdAt: number; + id: number; + lastError: string | null; + lastHealthCheck: number; + name: string; + shortId: string; + status: "error" | "mounted" | "unmounted"; + type: "directory" | "nfs" | "rclone" | "sftp" | "smb" | "webdav"; + updatedAt: number; + }; }; -export type CreateVolumeResponse = CreateVolumeResponses[keyof CreateVolumeResponses]; +export type CreateVolumeResponse = + CreateVolumeResponses[keyof CreateVolumeResponses]; export type TestConnectionData = { - body?: { - config: { - backend: 'directory'; - path: string; - readOnly?: false; - } | { - backend: 'nfs'; - exportPath: string; - server: string; - version: '3' | '4' | '4.1'; - port?: number; - readOnly?: boolean; - } | { - backend: 'rclone'; - path: string; - remote: string; - readOnly?: boolean; - } | { - backend: 'sftp'; - host: string; - path: string; - username: string; - port?: number; - skipHostKeyCheck?: boolean; - knownHosts?: string; - password?: string; - privateKey?: string; - readOnly?: boolean; - } | { - backend: 'smb'; - password: string; - server: string; - share: string; - username: string; - vers?: '1.0' | '2.0' | '2.1' | '3.0'; - port?: number; - domain?: string; - readOnly?: boolean; - } | { - backend: 'webdav'; - path: string; - server: string; - port?: number; - password?: string; - readOnly?: boolean; - ssl?: boolean; - username?: string; - }; - }; - path?: never; - query?: never; - url: '/api/v1/volumes/test-connection'; + body?: { + config: + | { + backend: "directory"; + path: string; + readOnly?: false; + } + | { + backend: "nfs"; + exportPath: string; + server: string; + version: "3" | "4" | "4.1"; + port?: number; + readOnly?: boolean; + } + | { + backend: "rclone"; + path: string; + remote: string; + readOnly?: boolean; + } + | { + backend: "sftp"; + host: string; + path: string; + username: string; + port?: number; + skipHostKeyCheck?: boolean; + knownHosts?: string; + password?: string; + privateKey?: string; + readOnly?: boolean; + } + | { + backend: "smb"; + password: string; + server: string; + share: string; + username: string; + vers?: "1.0" | "2.0" | "2.1" | "3.0"; + port?: number; + domain?: string; + readOnly?: boolean; + } + | { + backend: "webdav"; + path: string; + server: string; + port?: number; + password?: string; + readOnly?: boolean; + ssl?: boolean; + username?: string; + }; + }; + path?: never; + query?: never; + url: "/api/v1/volumes/test-connection"; }; export type TestConnectionResponses = { - /** - * Connection test result - */ - 200: { - message: string; - success: boolean; - }; + /** + * Connection test result + */ + 200: { + message: string; + success: boolean; + }; }; -export type TestConnectionResponse = TestConnectionResponses[keyof TestConnectionResponses]; +export type TestConnectionResponse = + TestConnectionResponses[keyof TestConnectionResponses]; export type DeleteVolumeData = { - body?: never; - path: { - name: string; - }; - query?: never; - url: '/api/v1/volumes/{name}'; + body?: never; + path: { + name: string; + }; + query?: never; + url: "/api/v1/volumes/{name}"; }; export type DeleteVolumeResponses = { - /** - * Volume deleted successfully - */ - 200: { - message: string; - }; + /** + * Volume deleted successfully + */ + 200: { + message: string; + }; }; -export type DeleteVolumeResponse = DeleteVolumeResponses[keyof DeleteVolumeResponses]; +export type DeleteVolumeResponse = + DeleteVolumeResponses[keyof DeleteVolumeResponses]; export type GetVolumeData = { - body?: never; - path: { - name: string; - }; - query?: never; - url: '/api/v1/volumes/{name}'; + body?: never; + path: { + name: string; + }; + query?: never; + url: "/api/v1/volumes/{name}"; }; export type GetVolumeErrors = { - /** - * Volume not found - */ - 404: unknown; + /** + * Volume not found + */ + 404: unknown; }; export type GetVolumeResponses = { - /** - * Volume details - */ - 200: { - statfs: { - free: number; - total: number; - used: number; - }; - volume: { - autoRemount: boolean; - config: { - backend: 'directory'; - path: string; - readOnly?: false; - } | { - backend: 'nfs'; - exportPath: string; - server: string; - version: '3' | '4' | '4.1'; - port?: number; - readOnly?: boolean; - } | { - backend: 'rclone'; - path: string; - remote: string; - readOnly?: boolean; - } | { - backend: 'sftp'; - host: string; - path: string; - username: string; - port?: number; - skipHostKeyCheck?: boolean; - knownHosts?: string; - password?: string; - privateKey?: string; - readOnly?: boolean; - } | { - backend: 'smb'; - password: string; - server: string; - share: string; - username: string; - vers?: '1.0' | '2.0' | '2.1' | '3.0'; - port?: number; - domain?: string; - readOnly?: boolean; - } | { - backend: 'webdav'; - path: string; - server: string; - port?: number; - password?: string; - readOnly?: boolean; - ssl?: boolean; - username?: string; - }; - createdAt: number; - id: number; - lastError: string | null; - lastHealthCheck: number; - name: string; - shortId: string; - status: 'error' | 'mounted' | 'unmounted'; - type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav'; - updatedAt: number; - }; - }; + /** + * Volume details + */ + 200: { + statfs: { + free: number; + total: number; + used: number; + }; + volume: { + autoRemount: boolean; + config: + | { + backend: "directory"; + path: string; + readOnly?: false; + } + | { + backend: "nfs"; + exportPath: string; + server: string; + version: "3" | "4" | "4.1"; + port?: number; + readOnly?: boolean; + } + | { + backend: "rclone"; + path: string; + remote: string; + readOnly?: boolean; + } + | { + backend: "sftp"; + host: string; + path: string; + username: string; + port?: number; + skipHostKeyCheck?: boolean; + knownHosts?: string; + password?: string; + privateKey?: string; + readOnly?: boolean; + } + | { + backend: "smb"; + password: string; + server: string; + share: string; + username: string; + vers?: "1.0" | "2.0" | "2.1" | "3.0"; + port?: number; + domain?: string; + readOnly?: boolean; + } + | { + backend: "webdav"; + path: string; + server: string; + port?: number; + password?: string; + readOnly?: boolean; + ssl?: boolean; + username?: string; + }; + createdAt: number; + id: number; + lastError: string | null; + lastHealthCheck: number; + name: string; + shortId: string; + status: "error" | "mounted" | "unmounted"; + type: "directory" | "nfs" | "rclone" | "sftp" | "smb" | "webdav"; + updatedAt: number; + }; + }; }; export type GetVolumeResponse = GetVolumeResponses[keyof GetVolumeResponses]; export type UpdateVolumeData = { - body?: { - autoRemount?: boolean; - config?: { - backend: 'directory'; - path: string; - readOnly?: false; - } | { - backend: 'nfs'; - exportPath: string; - server: string; - version: '3' | '4' | '4.1'; - port?: number; - readOnly?: boolean; - } | { - backend: 'rclone'; - path: string; - remote: string; - readOnly?: boolean; - } | { - backend: 'sftp'; - host: string; - path: string; - username: string; - port?: number; - skipHostKeyCheck?: boolean; - knownHosts?: string; - password?: string; - privateKey?: string; - readOnly?: boolean; - } | { - backend: 'smb'; - password: string; - server: string; - share: string; - username: string; - vers?: '1.0' | '2.0' | '2.1' | '3.0'; - port?: number; - domain?: string; - readOnly?: boolean; - } | { - backend: 'webdav'; - path: string; - server: string; - port?: number; - password?: string; - readOnly?: boolean; - ssl?: boolean; - username?: string; - }; - name?: string; - }; - path: { - name: string; - }; - query?: never; - url: '/api/v1/volumes/{name}'; + body?: { + autoRemount?: boolean; + config?: + | { + backend: "directory"; + path: string; + readOnly?: false; + } + | { + backend: "nfs"; + exportPath: string; + server: string; + version: "3" | "4" | "4.1"; + port?: number; + readOnly?: boolean; + } + | { + backend: "rclone"; + path: string; + remote: string; + readOnly?: boolean; + } + | { + backend: "sftp"; + host: string; + path: string; + username: string; + port?: number; + skipHostKeyCheck?: boolean; + knownHosts?: string; + password?: string; + privateKey?: string; + readOnly?: boolean; + } + | { + backend: "smb"; + password: string; + server: string; + share: string; + username: string; + vers?: "1.0" | "2.0" | "2.1" | "3.0"; + port?: number; + domain?: string; + readOnly?: boolean; + } + | { + backend: "webdav"; + path: string; + server: string; + port?: number; + password?: string; + readOnly?: boolean; + ssl?: boolean; + username?: string; + }; + name?: string; + }; + path: { + name: string; + }; + query?: never; + url: "/api/v1/volumes/{name}"; }; export type UpdateVolumeErrors = { - /** - * Volume not found - */ - 404: unknown; + /** + * Volume not found + */ + 404: unknown; }; export type UpdateVolumeResponses = { - /** - * Volume updated successfully - */ - 200: { - autoRemount: boolean; - config: { - backend: 'directory'; - path: string; - readOnly?: false; - } | { - backend: 'nfs'; - exportPath: string; - server: string; - version: '3' | '4' | '4.1'; - port?: number; - readOnly?: boolean; - } | { - backend: 'rclone'; - path: string; - remote: string; - readOnly?: boolean; - } | { - backend: 'sftp'; - host: string; - path: string; - username: string; - port?: number; - skipHostKeyCheck?: boolean; - knownHosts?: string; - password?: string; - privateKey?: string; - readOnly?: boolean; - } | { - backend: 'smb'; - password: string; - server: string; - share: string; - username: string; - vers?: '1.0' | '2.0' | '2.1' | '3.0'; - port?: number; - domain?: string; - readOnly?: boolean; - } | { - backend: 'webdav'; - path: string; - server: string; - port?: number; - password?: string; - readOnly?: boolean; - ssl?: boolean; - username?: string; - }; - createdAt: number; - id: number; - lastError: string | null; - lastHealthCheck: number; - name: string; - shortId: string; - status: 'error' | 'mounted' | 'unmounted'; - type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav'; - updatedAt: number; - }; + /** + * Volume updated successfully + */ + 200: { + autoRemount: boolean; + config: + | { + backend: "directory"; + path: string; + readOnly?: false; + } + | { + backend: "nfs"; + exportPath: string; + server: string; + version: "3" | "4" | "4.1"; + port?: number; + readOnly?: boolean; + } + | { + backend: "rclone"; + path: string; + remote: string; + readOnly?: boolean; + } + | { + backend: "sftp"; + host: string; + path: string; + username: string; + port?: number; + skipHostKeyCheck?: boolean; + knownHosts?: string; + password?: string; + privateKey?: string; + readOnly?: boolean; + } + | { + backend: "smb"; + password: string; + server: string; + share: string; + username: string; + vers?: "1.0" | "2.0" | "2.1" | "3.0"; + port?: number; + domain?: string; + readOnly?: boolean; + } + | { + backend: "webdav"; + path: string; + server: string; + port?: number; + password?: string; + readOnly?: boolean; + ssl?: boolean; + username?: string; + }; + createdAt: number; + id: number; + lastError: string | null; + lastHealthCheck: number; + name: string; + shortId: string; + status: "error" | "mounted" | "unmounted"; + type: "directory" | "nfs" | "rclone" | "sftp" | "smb" | "webdav"; + updatedAt: number; + }; }; -export type UpdateVolumeResponse = UpdateVolumeResponses[keyof UpdateVolumeResponses]; +export type UpdateVolumeResponse = + UpdateVolumeResponses[keyof UpdateVolumeResponses]; export type MountVolumeData = { - body?: never; - path: { - name: string; - }; - query?: never; - url: '/api/v1/volumes/{name}/mount'; + body?: never; + path: { + name: string; + }; + query?: never; + url: "/api/v1/volumes/{name}/mount"; }; export type MountVolumeResponses = { - /** - * Volume mounted successfully - */ - 200: { - status: 'error' | 'mounted' | 'unmounted'; - error?: string; - }; + /** + * Volume mounted successfully + */ + 200: { + status: "error" | "mounted" | "unmounted"; + error?: string; + }; }; -export type MountVolumeResponse = MountVolumeResponses[keyof MountVolumeResponses]; +export type MountVolumeResponse = + MountVolumeResponses[keyof MountVolumeResponses]; export type UnmountVolumeData = { - body?: never; - path: { - name: string; - }; - query?: never; - url: '/api/v1/volumes/{name}/unmount'; + body?: never; + path: { + name: string; + }; + query?: never; + url: "/api/v1/volumes/{name}/unmount"; }; export type UnmountVolumeResponses = { - /** - * Volume unmounted successfully - */ - 200: { - status: 'error' | 'mounted' | 'unmounted'; - error?: string; - }; + /** + * Volume unmounted successfully + */ + 200: { + status: "error" | "mounted" | "unmounted"; + error?: string; + }; }; -export type UnmountVolumeResponse = UnmountVolumeResponses[keyof UnmountVolumeResponses]; +export type UnmountVolumeResponse = + UnmountVolumeResponses[keyof UnmountVolumeResponses]; export type HealthCheckVolumeData = { - body?: never; - path: { - name: string; - }; - query?: never; - url: '/api/v1/volumes/{name}/health-check'; + body?: never; + path: { + name: string; + }; + query?: never; + url: "/api/v1/volumes/{name}/health-check"; }; export type HealthCheckVolumeErrors = { - /** - * Volume not found - */ - 404: unknown; + /** + * Volume not found + */ + 404: unknown; }; export type HealthCheckVolumeResponses = { - /** - * Volume health check result - */ - 200: { - status: 'error' | 'mounted' | 'unmounted'; - error?: string; - }; + /** + * Volume health check result + */ + 200: { + status: "error" | "mounted" | "unmounted"; + error?: string; + }; }; -export type HealthCheckVolumeResponse = HealthCheckVolumeResponses[keyof HealthCheckVolumeResponses]; +export type HealthCheckVolumeResponse = + HealthCheckVolumeResponses[keyof HealthCheckVolumeResponses]; export type ListFilesData = { - body?: never; - path: { - name: string; - }; - query?: { - /** - * Subdirectory path to list (relative to volume root) - */ - path?: string; - }; - url: '/api/v1/volumes/{name}/files'; + body?: never; + path: { + name: string; + }; + query?: { + /** + * Subdirectory path to list (relative to volume root) + */ + path?: string; + }; + url: "/api/v1/volumes/{name}/files"; }; export type ListFilesResponses = { - /** - * List of files in the volume - */ - 200: { - files: Array<{ - name: string; - path: string; - type: 'directory' | 'file'; - modifiedAt?: number; - size?: number; - }>; - path: string; - }; + /** + * List of files in the volume + */ + 200: { + files: Array<{ + name: string; + path: string; + type: "directory" | "file"; + modifiedAt?: number; + size?: number; + }>; + path: string; + }; }; export type ListFilesResponse = ListFilesResponses[keyof ListFilesResponses]; export type BrowseFilesystemData = { - body?: never; - path?: never; - query?: { - /** - * Directory path to browse (absolute path, defaults to /) - */ - path?: string; - }; - url: '/api/v1/volumes/filesystem/browse'; + body?: never; + path?: never; + query?: { + /** + * Directory path to browse (absolute path, defaults to /) + */ + path?: string; + }; + url: "/api/v1/volumes/filesystem/browse"; }; export type BrowseFilesystemResponses = { - /** - * List of directories in the specified path - */ - 200: { - directories: Array<{ - name: string; - path: string; - type: 'directory' | 'file'; - modifiedAt?: number; - size?: number; - }>; - path: string; - }; + /** + * List of directories in the specified path + */ + 200: { + directories: Array<{ + name: string; + path: string; + type: "directory" | "file"; + modifiedAt?: number; + size?: number; + }>; + path: string; + }; }; -export type BrowseFilesystemResponse = BrowseFilesystemResponses[keyof BrowseFilesystemResponses]; +export type BrowseFilesystemResponse = + BrowseFilesystemResponses[keyof BrowseFilesystemResponses]; export type ListRepositoriesData = { - body?: never; - path?: never; - query?: never; - url: '/api/v1/repositories'; + body?: never; + path?: never; + query?: never; + url: "/api/v1/repositories"; }; export type ListRepositoriesResponses = { - /** - * List of repositories - */ - 200: Array<{ - compressionMode: 'auto' | 'max' | 'off' | null; - config: { - accessKeyId: string; - backend: 'r2'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accessKeyId: string; - backend: 's3'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accountKey: string; - accountName: string; - backend: 'azure'; - container: string; - cacert?: string; - customPassword?: string; - endpointSuffix?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'gcs'; - bucket: string; - credentialsJson: string; - projectId: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'local'; - name: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - path?: string; - } | { - backend: 'rclone'; - path: string; - remote: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'rest'; - url: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - password?: string; - path?: string; - username?: string; - } | { - backend: 'sftp'; - host: string; - path: string; - privateKey: string; - user: string; - port?: number; - skipHostKeyCheck?: boolean; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - knownHosts?: string; - }; - createdAt: number; - id: string; - lastChecked: number | null; - lastError: string | null; - name: string; - shortId: string; - status: 'error' | 'healthy' | 'unknown' | null; - type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp'; - updatedAt: number; - }>; + /** + * List of repositories + */ + 200: Array<{ + compressionMode: "auto" | "max" | "off" | null; + config: + | { + accessKeyId: string; + backend: "r2"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accessKeyId: string; + backend: "s3"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accountKey: string; + accountName: string; + backend: "azure"; + container: string; + cacert?: string; + customPassword?: string; + endpointSuffix?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "gcs"; + bucket: string; + credentialsJson: string; + projectId: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "local"; + name: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + path?: string; + } + | { + backend: "rclone"; + path: string; + remote: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "rest"; + url: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + password?: string; + path?: string; + username?: string; + } + | { + backend: "sftp"; + host: string; + path: string; + privateKey: string; + user: string; + port?: number; + skipHostKeyCheck?: boolean; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + knownHosts?: string; + }; + createdAt: number; + id: string; + lastChecked: number | null; + lastError: string | null; + name: string; + shortId: string; + status: "error" | "healthy" | "unknown" | null; + type: "azure" | "gcs" | "local" | "r2" | "rclone" | "rest" | "s3" | "sftp"; + updatedAt: number; + }>; }; -export type ListRepositoriesResponse = ListRepositoriesResponses[keyof ListRepositoriesResponses]; +export type ListRepositoriesResponse = + ListRepositoriesResponses[keyof ListRepositoriesResponses]; export type CreateRepositoryData = { - body?: { - config: { - accessKeyId: string; - backend: 'r2'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accessKeyId: string; - backend: 's3'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accountKey: string; - accountName: string; - backend: 'azure'; - container: string; - cacert?: string; - customPassword?: string; - endpointSuffix?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'gcs'; - bucket: string; - credentialsJson: string; - projectId: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'local'; - name: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - path?: string; - } | { - backend: 'rclone'; - path: string; - remote: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'rest'; - url: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - password?: string; - path?: string; - username?: string; - } | { - backend: 'sftp'; - host: string; - path: string; - privateKey: string; - user: string; - port?: number; - skipHostKeyCheck?: boolean; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - knownHosts?: string; - }; - name: string; - compressionMode?: 'auto' | 'max' | 'off'; - }; - path?: never; - query?: never; - url: '/api/v1/repositories'; + body?: { + config: + | { + accessKeyId: string; + backend: "r2"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accessKeyId: string; + backend: "s3"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accountKey: string; + accountName: string; + backend: "azure"; + container: string; + cacert?: string; + customPassword?: string; + endpointSuffix?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "gcs"; + bucket: string; + credentialsJson: string; + projectId: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "local"; + name: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + path?: string; + } + | { + backend: "rclone"; + path: string; + remote: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "rest"; + url: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + password?: string; + path?: string; + username?: string; + } + | { + backend: "sftp"; + host: string; + path: string; + privateKey: string; + user: string; + port?: number; + skipHostKeyCheck?: boolean; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + knownHosts?: string; + }; + name: string; + compressionMode?: "auto" | "max" | "off"; + }; + path?: never; + query?: never; + url: "/api/v1/repositories"; }; export type CreateRepositoryResponses = { - /** - * Repository created successfully - */ - 201: { - message: string; - repository: { - id: string; - name: string; - shortId: string; - }; - }; + /** + * Repository created successfully + */ + 201: { + message: string; + repository: { + id: string; + name: string; + shortId: string; + }; + }; }; -export type CreateRepositoryResponse = CreateRepositoryResponses[keyof CreateRepositoryResponses]; +export type CreateRepositoryResponse = + CreateRepositoryResponses[keyof CreateRepositoryResponses]; export type ListRcloneRemotesData = { - body?: never; - path?: never; - query?: never; - url: '/api/v1/repositories/rclone-remotes'; + body?: never; + path?: never; + query?: never; + url: "/api/v1/repositories/rclone-remotes"; }; export type ListRcloneRemotesResponses = { - /** - * List of rclone remotes - */ - 200: Array<{ - name: string; - type: string; - }>; + /** + * List of rclone remotes + */ + 200: Array<{ + name: string; + type: string; + }>; }; -export type ListRcloneRemotesResponse = ListRcloneRemotesResponses[keyof ListRcloneRemotesResponses]; +export type ListRcloneRemotesResponse = + ListRcloneRemotesResponses[keyof ListRcloneRemotesResponses]; export type DeleteRepositoryData = { - body?: never; - path: { - id: string; - }; - query?: never; - url: '/api/v1/repositories/{id}'; + body?: never; + path: { + id: string; + }; + query?: never; + url: "/api/v1/repositories/{id}"; }; export type DeleteRepositoryResponses = { - /** - * Repository deleted successfully - */ - 200: { - message: string; - }; + /** + * Repository deleted successfully + */ + 200: { + message: string; + }; }; -export type DeleteRepositoryResponse = DeleteRepositoryResponses[keyof DeleteRepositoryResponses]; +export type DeleteRepositoryResponse = + DeleteRepositoryResponses[keyof DeleteRepositoryResponses]; export type GetRepositoryData = { - body?: never; - path: { - id: string; - }; - query?: never; - url: '/api/v1/repositories/{id}'; + body?: never; + path: { + id: string; + }; + query?: never; + url: "/api/v1/repositories/{id}"; }; export type GetRepositoryResponses = { - /** - * Repository details - */ - 200: { - compressionMode: 'auto' | 'max' | 'off' | null; - config: { - accessKeyId: string; - backend: 'r2'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accessKeyId: string; - backend: 's3'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accountKey: string; - accountName: string; - backend: 'azure'; - container: string; - cacert?: string; - customPassword?: string; - endpointSuffix?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'gcs'; - bucket: string; - credentialsJson: string; - projectId: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'local'; - name: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - path?: string; - } | { - backend: 'rclone'; - path: string; - remote: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'rest'; - url: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - password?: string; - path?: string; - username?: string; - } | { - backend: 'sftp'; - host: string; - path: string; - privateKey: string; - user: string; - port?: number; - skipHostKeyCheck?: boolean; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - knownHosts?: string; - }; - createdAt: number; - id: string; - lastChecked: number | null; - lastError: string | null; - name: string; - shortId: string; - status: 'error' | 'healthy' | 'unknown' | null; - type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp'; - updatedAt: number; - }; + /** + * Repository details + */ + 200: { + compressionMode: "auto" | "max" | "off" | null; + config: + | { + accessKeyId: string; + backend: "r2"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accessKeyId: string; + backend: "s3"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accountKey: string; + accountName: string; + backend: "azure"; + container: string; + cacert?: string; + customPassword?: string; + endpointSuffix?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "gcs"; + bucket: string; + credentialsJson: string; + projectId: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "local"; + name: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + path?: string; + } + | { + backend: "rclone"; + path: string; + remote: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "rest"; + url: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + password?: string; + path?: string; + username?: string; + } + | { + backend: "sftp"; + host: string; + path: string; + privateKey: string; + user: string; + port?: number; + skipHostKeyCheck?: boolean; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + knownHosts?: string; + }; + createdAt: number; + id: string; + lastChecked: number | null; + lastError: string | null; + name: string; + shortId: string; + status: "error" | "healthy" | "unknown" | null; + type: "azure" | "gcs" | "local" | "r2" | "rclone" | "rest" | "s3" | "sftp"; + updatedAt: number; + }; }; -export type GetRepositoryResponse = GetRepositoryResponses[keyof GetRepositoryResponses]; +export type GetRepositoryResponse = + GetRepositoryResponses[keyof GetRepositoryResponses]; export type UpdateRepositoryData = { - body?: { - compressionMode?: 'auto' | 'max' | 'off'; - name?: string; - }; - path: { - id: string; - }; - query?: never; - url: '/api/v1/repositories/{id}'; + body?: { + compressionMode?: "auto" | "max" | "off"; + name?: string; + }; + path: { + id: string; + }; + query?: never; + url: "/api/v1/repositories/{id}"; }; export type UpdateRepositoryErrors = { - /** - * Repository not found - */ - 404: unknown; - /** - * Repository with this name already exists - */ - 409: unknown; + /** + * Repository not found + */ + 404: unknown; + /** + * Repository with this name already exists + */ + 409: unknown; }; export type UpdateRepositoryResponses = { - /** - * Repository updated successfully - */ - 200: { - compressionMode: 'auto' | 'max' | 'off' | null; - config: { - accessKeyId: string; - backend: 'r2'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accessKeyId: string; - backend: 's3'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accountKey: string; - accountName: string; - backend: 'azure'; - container: string; - cacert?: string; - customPassword?: string; - endpointSuffix?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'gcs'; - bucket: string; - credentialsJson: string; - projectId: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'local'; - name: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - path?: string; - } | { - backend: 'rclone'; - path: string; - remote: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'rest'; - url: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - password?: string; - path?: string; - username?: string; - } | { - backend: 'sftp'; - host: string; - path: string; - privateKey: string; - user: string; - port?: number; - skipHostKeyCheck?: boolean; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - knownHosts?: string; - }; - createdAt: number; - id: string; - lastChecked: number | null; - lastError: string | null; - name: string; - shortId: string; - status: 'error' | 'healthy' | 'unknown' | null; - type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp'; - updatedAt: number; - }; + /** + * Repository updated successfully + */ + 200: { + compressionMode: "auto" | "max" | "off" | null; + config: + | { + accessKeyId: string; + backend: "r2"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accessKeyId: string; + backend: "s3"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accountKey: string; + accountName: string; + backend: "azure"; + container: string; + cacert?: string; + customPassword?: string; + endpointSuffix?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "gcs"; + bucket: string; + credentialsJson: string; + projectId: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "local"; + name: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + path?: string; + } + | { + backend: "rclone"; + path: string; + remote: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "rest"; + url: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + password?: string; + path?: string; + username?: string; + } + | { + backend: "sftp"; + host: string; + path: string; + privateKey: string; + user: string; + port?: number; + skipHostKeyCheck?: boolean; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + knownHosts?: string; + }; + createdAt: number; + id: string; + lastChecked: number | null; + lastError: string | null; + name: string; + shortId: string; + status: "error" | "healthy" | "unknown" | null; + type: "azure" | "gcs" | "local" | "r2" | "rclone" | "rest" | "s3" | "sftp"; + updatedAt: number; + }; }; -export type UpdateRepositoryResponse = UpdateRepositoryResponses[keyof UpdateRepositoryResponses]; +export type UpdateRepositoryResponse = + UpdateRepositoryResponses[keyof UpdateRepositoryResponses]; export type DeleteSnapshotsData = { - body?: { - snapshotIds: Array; - }; - path: { - id: string; - }; - query?: never; - url: '/api/v1/repositories/{id}/snapshots'; + body?: { + snapshotIds: Array; + }; + path: { + id: string; + }; + query?: never; + url: "/api/v1/repositories/{id}/snapshots"; }; export type DeleteSnapshotsResponses = { - /** - * Snapshots deleted successfully - */ - 200: { - message: string; - }; + /** + * Snapshots deleted successfully + */ + 200: { + message: string; + }; }; -export type DeleteSnapshotsResponse = DeleteSnapshotsResponses[keyof DeleteSnapshotsResponses]; +export type DeleteSnapshotsResponse = + DeleteSnapshotsResponses[keyof DeleteSnapshotsResponses]; export type ListSnapshotsData = { - body?: never; - path: { - id: string; - }; - query?: { - backupId?: string; - }; - url: '/api/v1/repositories/{id}/snapshots'; + body?: never; + path: { + id: string; + }; + query?: { + backupId?: string; + }; + url: "/api/v1/repositories/{id}/snapshots"; }; export type ListSnapshotsResponses = { - /** - * List of snapshots - */ - 200: Array<{ - duration: number; - paths: Array; - short_id: string; - size: number; - tags: Array; - time: number; - }>; + /** + * List of snapshots + */ + 200: Array<{ + duration: number; + paths: Array; + short_id: string; + size: number; + tags: Array; + time: number; + }>; }; -export type ListSnapshotsResponse = ListSnapshotsResponses[keyof ListSnapshotsResponses]; +export type ListSnapshotsResponse = + ListSnapshotsResponses[keyof ListSnapshotsResponses]; export type DeleteSnapshotData = { - body?: never; - path: { - id: string; - snapshotId: string; - }; - query?: never; - url: '/api/v1/repositories/{id}/snapshots/{snapshotId}'; + body?: never; + path: { + id: string; + snapshotId: string; + }; + query?: never; + url: "/api/v1/repositories/{id}/snapshots/{snapshotId}"; }; export type DeleteSnapshotResponses = { - /** - * Snapshot deleted successfully - */ - 200: { - message: string; - }; + /** + * Snapshot deleted successfully + */ + 200: { + message: string; + }; }; -export type DeleteSnapshotResponse = DeleteSnapshotResponses[keyof DeleteSnapshotResponses]; +export type DeleteSnapshotResponse = + DeleteSnapshotResponses[keyof DeleteSnapshotResponses]; export type GetSnapshotDetailsData = { - body?: never; - path: { - id: string; - snapshotId: string; - }; - query?: never; - url: '/api/v1/repositories/{id}/snapshots/{snapshotId}'; + body?: never; + path: { + id: string; + snapshotId: string; + }; + query?: never; + url: "/api/v1/repositories/{id}/snapshots/{snapshotId}"; }; export type GetSnapshotDetailsResponses = { - /** - * Snapshot details - */ - 200: { - duration: number; - paths: Array; - short_id: string; - size: number; - tags: Array; - time: number; - }; + /** + * Snapshot details + */ + 200: { + duration: number; + paths: Array; + short_id: string; + size: number; + tags: Array; + time: number; + }; }; -export type GetSnapshotDetailsResponse = GetSnapshotDetailsResponses[keyof GetSnapshotDetailsResponses]; +export type GetSnapshotDetailsResponse = + GetSnapshotDetailsResponses[keyof GetSnapshotDetailsResponses]; export type ListSnapshotFilesData = { - body?: never; - path: { - id: string; - snapshotId: string; - }; - query?: { - path?: string; - }; - url: '/api/v1/repositories/{id}/snapshots/{snapshotId}/files'; + body?: never; + path: { + id: string; + snapshotId: string; + }; + query?: { + path?: string; + }; + url: "/api/v1/repositories/{id}/snapshots/{snapshotId}/files"; }; export type ListSnapshotFilesResponses = { - /** - * List of files and directories in the snapshot - */ - 200: { - files: Array<{ - name: string; - path: string; - type: string; - atime?: string; - ctime?: string; - gid?: number; - mode?: number; - mtime?: string; - size?: number; - uid?: number; - }>; - snapshot: { - hostname: string; - id: string; - paths: Array; - short_id: string; - time: string; - }; - }; + /** + * List of files and directories in the snapshot + */ + 200: { + files: Array<{ + name: string; + path: string; + type: string; + atime?: string; + ctime?: string; + gid?: number; + mode?: number; + mtime?: string; + size?: number; + uid?: number; + }>; + snapshot: { + hostname: string; + id: string; + paths: Array; + short_id: string; + time: string; + }; + }; }; -export type ListSnapshotFilesResponse = ListSnapshotFilesResponses[keyof ListSnapshotFilesResponses]; +export type ListSnapshotFilesResponse = + ListSnapshotFilesResponses[keyof ListSnapshotFilesResponses]; export type RestoreSnapshotData = { - body?: { - snapshotId: string; - delete?: boolean; - exclude?: Array; - excludeXattr?: Array; - include?: Array; - overwrite?: 'always' | 'if-changed' | 'if-newer' | 'never'; - targetPath?: string; - }; - path: { - id: string; - }; - query?: never; - url: '/api/v1/repositories/{id}/restore'; + body?: { + snapshotId: string; + delete?: boolean; + exclude?: Array; + excludeXattr?: Array; + include?: Array; + overwrite?: "always" | "if-changed" | "if-newer" | "never"; + targetPath?: string; + }; + path: { + id: string; + }; + query?: never; + url: "/api/v1/repositories/{id}/restore"; }; export type RestoreSnapshotResponses = { - /** - * Snapshot restored successfully - */ - 200: { - filesRestored: number; - filesSkipped: number; - message: string; - success: boolean; - }; + /** + * Snapshot restored successfully + */ + 200: { + filesRestored: number; + filesSkipped: number; + message: string; + success: boolean; + }; }; -export type RestoreSnapshotResponse = RestoreSnapshotResponses[keyof RestoreSnapshotResponses]; +export type RestoreSnapshotResponse = + RestoreSnapshotResponses[keyof RestoreSnapshotResponses]; export type DoctorRepositoryData = { - body?: never; - path: { - id: string; - }; - query?: never; - url: '/api/v1/repositories/{id}/doctor'; + body?: never; + path: { + id: string; + }; + query?: never; + url: "/api/v1/repositories/{id}/doctor"; }; export type DoctorRepositoryResponses = { - /** - * Doctor operation completed - */ - 200: { - steps: Array<{ - error: string | null; - output: string | null; - step: string; - success: boolean; - }>; - success: boolean; - }; + /** + * Doctor operation completed + */ + 200: { + steps: Array<{ + error: string | null; + output: string | null; + step: string; + success: boolean; + }>; + success: boolean; + }; }; -export type DoctorRepositoryResponse = DoctorRepositoryResponses[keyof DoctorRepositoryResponses]; +export type DoctorRepositoryResponse = + DoctorRepositoryResponses[keyof DoctorRepositoryResponses]; export type TagSnapshotsData = { - body?: { - snapshotIds: Array; - add?: Array; - remove?: Array; - set?: Array; - }; - path: { - id: string; - }; - query?: never; - url: '/api/v1/repositories/{id}/snapshots/tag'; + body?: { + snapshotIds: Array; + add?: Array; + remove?: Array; + set?: Array; + }; + path: { + id: string; + }; + query?: never; + url: "/api/v1/repositories/{id}/snapshots/tag"; }; export type TagSnapshotsResponses = { - /** - * Snapshots tagged successfully - */ - 200: { - message: string; - }; + /** + * Snapshots tagged successfully + */ + 200: { + message: string; + }; }; -export type TagSnapshotsResponse = TagSnapshotsResponses[keyof TagSnapshotsResponses]; +export type TagSnapshotsResponse = + TagSnapshotsResponses[keyof TagSnapshotsResponses]; export type ListBackupSchedulesData = { - body?: never; - path?: never; - query?: never; - url: '/api/v1/backups'; + body?: never; + path?: never; + query?: never; + url: "/api/v1/backups"; }; export type ListBackupSchedulesResponses = { - /** - * List of backup schedules - */ - 200: Array<{ - createdAt: number; - cronExpression: string; - enabled: boolean; - excludeIfPresent: Array | null; - excludePatterns: Array | null; - id: number; - includePatterns: Array | null; - lastBackupAt: number | null; - lastBackupError: string | null; - lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null; - name: string; - nextBackupAt: number | null; - oneFileSystem: boolean; - repository: { - compressionMode: 'auto' | 'max' | 'off' | null; - config: { - accessKeyId: string; - backend: 'r2'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accessKeyId: string; - backend: 's3'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accountKey: string; - accountName: string; - backend: 'azure'; - container: string; - cacert?: string; - customPassword?: string; - endpointSuffix?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'gcs'; - bucket: string; - credentialsJson: string; - projectId: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'local'; - name: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - path?: string; - } | { - backend: 'rclone'; - path: string; - remote: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'rest'; - url: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - password?: string; - path?: string; - username?: string; - } | { - backend: 'sftp'; - host: string; - path: string; - privateKey: string; - user: string; - port?: number; - skipHostKeyCheck?: boolean; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - knownHosts?: string; - }; - createdAt: number; - id: string; - lastChecked: number | null; - lastError: string | null; - name: string; - shortId: string; - status: 'error' | 'healthy' | 'unknown' | null; - type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp'; - updatedAt: number; - }; - repositoryId: string; - retentionPolicy: { - keepDaily?: number; - keepHourly?: number; - keepLast?: number; - keepMonthly?: number; - keepWeekly?: number; - keepWithinDuration?: string; - keepYearly?: number; - } | null; - shortId: string; - updatedAt: number; - volume: { - autoRemount: boolean; - config: { - backend: 'directory'; - path: string; - readOnly?: false; - } | { - backend: 'nfs'; - exportPath: string; - server: string; - version: '3' | '4' | '4.1'; - port?: number; - readOnly?: boolean; - } | { - backend: 'rclone'; - path: string; - remote: string; - readOnly?: boolean; - } | { - backend: 'sftp'; - host: string; - path: string; - username: string; - port?: number; - skipHostKeyCheck?: boolean; - knownHosts?: string; - password?: string; - privateKey?: string; - readOnly?: boolean; - } | { - backend: 'smb'; - password: string; - server: string; - share: string; - username: string; - vers?: '1.0' | '2.0' | '2.1' | '3.0'; - port?: number; - domain?: string; - readOnly?: boolean; - } | { - backend: 'webdav'; - path: string; - server: string; - port?: number; - password?: string; - readOnly?: boolean; - ssl?: boolean; - username?: string; - }; - createdAt: number; - id: number; - lastError: string | null; - lastHealthCheck: number; - name: string; - shortId: string; - status: 'error' | 'mounted' | 'unmounted'; - type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav'; - updatedAt: number; - }; - volumeId: number; - }>; + /** + * List of backup schedules + */ + 200: Array<{ + createdAt: number; + cronExpression: string; + enabled: boolean; + excludeIfPresent: Array | null; + excludePatterns: Array | null; + id: number; + includePatterns: Array | null; + lastBackupAt: number | null; + lastBackupError: string | null; + lastBackupStatus: "error" | "in_progress" | "success" | "warning" | null; + name: string; + nextBackupAt: number | null; + oneFileSystem: boolean; + repository: { + compressionMode: "auto" | "max" | "off" | null; + config: + | { + accessKeyId: string; + backend: "r2"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accessKeyId: string; + backend: "s3"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accountKey: string; + accountName: string; + backend: "azure"; + container: string; + cacert?: string; + customPassword?: string; + endpointSuffix?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "gcs"; + bucket: string; + credentialsJson: string; + projectId: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "local"; + name: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + path?: string; + } + | { + backend: "rclone"; + path: string; + remote: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "rest"; + url: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + password?: string; + path?: string; + username?: string; + } + | { + backend: "sftp"; + host: string; + path: string; + privateKey: string; + user: string; + port?: number; + skipHostKeyCheck?: boolean; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + knownHosts?: string; + }; + createdAt: number; + id: string; + lastChecked: number | null; + lastError: string | null; + name: string; + shortId: string; + status: "error" | "healthy" | "unknown" | null; + type: + | "azure" + | "gcs" + | "local" + | "r2" + | "rclone" + | "rest" + | "s3" + | "sftp"; + updatedAt: number; + }; + repositoryId: string; + retentionPolicy: { + keepDaily?: number; + keepHourly?: number; + keepLast?: number; + keepMonthly?: number; + keepWeekly?: number; + keepWithinDuration?: string; + keepYearly?: number; + } | null; + shortId: string; + updatedAt: number; + volume: { + autoRemount: boolean; + config: + | { + backend: "directory"; + path: string; + readOnly?: false; + } + | { + backend: "nfs"; + exportPath: string; + server: string; + version: "3" | "4" | "4.1"; + port?: number; + readOnly?: boolean; + } + | { + backend: "rclone"; + path: string; + remote: string; + readOnly?: boolean; + } + | { + backend: "sftp"; + host: string; + path: string; + username: string; + port?: number; + skipHostKeyCheck?: boolean; + knownHosts?: string; + password?: string; + privateKey?: string; + readOnly?: boolean; + } + | { + backend: "smb"; + password: string; + server: string; + share: string; + username: string; + vers?: "1.0" | "2.0" | "2.1" | "3.0"; + port?: number; + domain?: string; + readOnly?: boolean; + } + | { + backend: "webdav"; + path: string; + server: string; + port?: number; + password?: string; + readOnly?: boolean; + ssl?: boolean; + username?: string; + }; + createdAt: number; + id: number; + lastError: string | null; + lastHealthCheck: number; + name: string; + shortId: string; + status: "error" | "mounted" | "unmounted"; + type: "directory" | "nfs" | "rclone" | "sftp" | "smb" | "webdav"; + updatedAt: number; + }; + volumeId: number; + }>; }; -export type ListBackupSchedulesResponse = ListBackupSchedulesResponses[keyof ListBackupSchedulesResponses]; +export type ListBackupSchedulesResponse = + ListBackupSchedulesResponses[keyof ListBackupSchedulesResponses]; export type CreateBackupScheduleData = { - body?: { - cronExpression: string; - enabled: boolean; - name: string; - repositoryId: string; - volumeId: number; - excludeIfPresent?: Array; - excludePatterns?: Array; - includePatterns?: Array; - oneFileSystem?: boolean; - retentionPolicy?: { - keepDaily?: number; - keepHourly?: number; - keepLast?: number; - keepMonthly?: number; - keepWeekly?: number; - keepWithinDuration?: string; - keepYearly?: number; - }; - tags?: Array; - }; - path?: never; - query?: never; - url: '/api/v1/backups'; + body?: { + cronExpression: string; + enabled: boolean; + name: string; + repositoryId: string; + volumeId: number; + excludeIfPresent?: Array; + excludePatterns?: Array; + includePatterns?: Array; + oneFileSystem?: boolean; + retentionPolicy?: { + keepDaily?: number; + keepHourly?: number; + keepLast?: number; + keepMonthly?: number; + keepWeekly?: number; + keepWithinDuration?: string; + keepYearly?: number; + }; + tags?: Array; + }; + path?: never; + query?: never; + url: "/api/v1/backups"; }; export type CreateBackupScheduleResponses = { - /** - * Backup schedule created successfully - */ - 201: { - createdAt: number; - cronExpression: string; - enabled: boolean; - excludeIfPresent: Array | null; - excludePatterns: Array | null; - id: number; - includePatterns: Array | null; - lastBackupAt: number | null; - lastBackupError: string | null; - lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null; - name: string; - nextBackupAt: number | null; - oneFileSystem: boolean; - repositoryId: string; - retentionPolicy: { - keepDaily?: number; - keepHourly?: number; - keepLast?: number; - keepMonthly?: number; - keepWeekly?: number; - keepWithinDuration?: string; - keepYearly?: number; - } | null; - shortId: string; - updatedAt: number; - volumeId: number; - }; + /** + * Backup schedule created successfully + */ + 201: { + createdAt: number; + cronExpression: string; + enabled: boolean; + excludeIfPresent: Array | null; + excludePatterns: Array | null; + id: number; + includePatterns: Array | null; + lastBackupAt: number | null; + lastBackupError: string | null; + lastBackupStatus: "error" | "in_progress" | "success" | "warning" | null; + name: string; + nextBackupAt: number | null; + oneFileSystem: boolean; + repositoryId: string; + retentionPolicy: { + keepDaily?: number; + keepHourly?: number; + keepLast?: number; + keepMonthly?: number; + keepWeekly?: number; + keepWithinDuration?: string; + keepYearly?: number; + } | null; + shortId: string; + updatedAt: number; + volumeId: number; + }; }; -export type CreateBackupScheduleResponse = CreateBackupScheduleResponses[keyof CreateBackupScheduleResponses]; +export type CreateBackupScheduleResponse = + CreateBackupScheduleResponses[keyof CreateBackupScheduleResponses]; export type DeleteBackupScheduleData = { - body?: never; - path: { - scheduleId: string; - }; - query?: never; - url: '/api/v1/backups/{scheduleId}'; + body?: never; + path: { + scheduleId: string; + }; + query?: never; + url: "/api/v1/backups/{scheduleId}"; }; export type DeleteBackupScheduleResponses = { - /** - * Backup schedule deleted successfully - */ - 200: { - success: boolean; - }; + /** + * Backup schedule deleted successfully + */ + 200: { + success: boolean; + }; }; -export type DeleteBackupScheduleResponse = DeleteBackupScheduleResponses[keyof DeleteBackupScheduleResponses]; +export type DeleteBackupScheduleResponse = + DeleteBackupScheduleResponses[keyof DeleteBackupScheduleResponses]; export type GetBackupScheduleData = { - body?: never; - path: { - scheduleId: string; - }; - query?: never; - url: '/api/v1/backups/{scheduleId}'; + body?: never; + path: { + scheduleId: string; + }; + query?: never; + url: "/api/v1/backups/{scheduleId}"; }; export type GetBackupScheduleResponses = { - /** - * Backup schedule details - */ - 200: { - createdAt: number; - cronExpression: string; - enabled: boolean; - excludeIfPresent: Array | null; - excludePatterns: Array | null; - id: number; - includePatterns: Array | null; - lastBackupAt: number | null; - lastBackupError: string | null; - lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null; - name: string; - nextBackupAt: number | null; - oneFileSystem: boolean; - repository: { - compressionMode: 'auto' | 'max' | 'off' | null; - config: { - accessKeyId: string; - backend: 'r2'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accessKeyId: string; - backend: 's3'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accountKey: string; - accountName: string; - backend: 'azure'; - container: string; - cacert?: string; - customPassword?: string; - endpointSuffix?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'gcs'; - bucket: string; - credentialsJson: string; - projectId: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'local'; - name: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - path?: string; - } | { - backend: 'rclone'; - path: string; - remote: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'rest'; - url: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - password?: string; - path?: string; - username?: string; - } | { - backend: 'sftp'; - host: string; - path: string; - privateKey: string; - user: string; - port?: number; - skipHostKeyCheck?: boolean; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - knownHosts?: string; - }; - createdAt: number; - id: string; - lastChecked: number | null; - lastError: string | null; - name: string; - shortId: string; - status: 'error' | 'healthy' | 'unknown' | null; - type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp'; - updatedAt: number; - }; - repositoryId: string; - retentionPolicy: { - keepDaily?: number; - keepHourly?: number; - keepLast?: number; - keepMonthly?: number; - keepWeekly?: number; - keepWithinDuration?: string; - keepYearly?: number; - } | null; - shortId: string; - updatedAt: number; - volume: { - autoRemount: boolean; - config: { - backend: 'directory'; - path: string; - readOnly?: false; - } | { - backend: 'nfs'; - exportPath: string; - server: string; - version: '3' | '4' | '4.1'; - port?: number; - readOnly?: boolean; - } | { - backend: 'rclone'; - path: string; - remote: string; - readOnly?: boolean; - } | { - backend: 'sftp'; - host: string; - path: string; - username: string; - port?: number; - skipHostKeyCheck?: boolean; - knownHosts?: string; - password?: string; - privateKey?: string; - readOnly?: boolean; - } | { - backend: 'smb'; - password: string; - server: string; - share: string; - username: string; - vers?: '1.0' | '2.0' | '2.1' | '3.0'; - port?: number; - domain?: string; - readOnly?: boolean; - } | { - backend: 'webdav'; - path: string; - server: string; - port?: number; - password?: string; - readOnly?: boolean; - ssl?: boolean; - username?: string; - }; - createdAt: number; - id: number; - lastError: string | null; - lastHealthCheck: number; - name: string; - shortId: string; - status: 'error' | 'mounted' | 'unmounted'; - type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav'; - updatedAt: number; - }; - volumeId: number; - }; + /** + * Backup schedule details + */ + 200: { + createdAt: number; + cronExpression: string; + enabled: boolean; + excludeIfPresent: Array | null; + excludePatterns: Array | null; + id: number; + includePatterns: Array | null; + lastBackupAt: number | null; + lastBackupError: string | null; + lastBackupStatus: "error" | "in_progress" | "success" | "warning" | null; + name: string; + nextBackupAt: number | null; + oneFileSystem: boolean; + repository: { + compressionMode: "auto" | "max" | "off" | null; + config: + | { + accessKeyId: string; + backend: "r2"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accessKeyId: string; + backend: "s3"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accountKey: string; + accountName: string; + backend: "azure"; + container: string; + cacert?: string; + customPassword?: string; + endpointSuffix?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "gcs"; + bucket: string; + credentialsJson: string; + projectId: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "local"; + name: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + path?: string; + } + | { + backend: "rclone"; + path: string; + remote: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "rest"; + url: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + password?: string; + path?: string; + username?: string; + } + | { + backend: "sftp"; + host: string; + path: string; + privateKey: string; + user: string; + port?: number; + skipHostKeyCheck?: boolean; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + knownHosts?: string; + }; + createdAt: number; + id: string; + lastChecked: number | null; + lastError: string | null; + name: string; + shortId: string; + status: "error" | "healthy" | "unknown" | null; + type: + | "azure" + | "gcs" + | "local" + | "r2" + | "rclone" + | "rest" + | "s3" + | "sftp"; + updatedAt: number; + }; + repositoryId: string; + retentionPolicy: { + keepDaily?: number; + keepHourly?: number; + keepLast?: number; + keepMonthly?: number; + keepWeekly?: number; + keepWithinDuration?: string; + keepYearly?: number; + } | null; + shortId: string; + updatedAt: number; + volume: { + autoRemount: boolean; + config: + | { + backend: "directory"; + path: string; + readOnly?: false; + } + | { + backend: "nfs"; + exportPath: string; + server: string; + version: "3" | "4" | "4.1"; + port?: number; + readOnly?: boolean; + } + | { + backend: "rclone"; + path: string; + remote: string; + readOnly?: boolean; + } + | { + backend: "sftp"; + host: string; + path: string; + username: string; + port?: number; + skipHostKeyCheck?: boolean; + knownHosts?: string; + password?: string; + privateKey?: string; + readOnly?: boolean; + } + | { + backend: "smb"; + password: string; + server: string; + share: string; + username: string; + vers?: "1.0" | "2.0" | "2.1" | "3.0"; + port?: number; + domain?: string; + readOnly?: boolean; + } + | { + backend: "webdav"; + path: string; + server: string; + port?: number; + password?: string; + readOnly?: boolean; + ssl?: boolean; + username?: string; + }; + createdAt: number; + id: number; + lastError: string | null; + lastHealthCheck: number; + name: string; + shortId: string; + status: "error" | "mounted" | "unmounted"; + type: "directory" | "nfs" | "rclone" | "sftp" | "smb" | "webdav"; + updatedAt: number; + }; + volumeId: number; + }; }; -export type GetBackupScheduleResponse = GetBackupScheduleResponses[keyof GetBackupScheduleResponses]; +export type GetBackupScheduleResponse = + GetBackupScheduleResponses[keyof GetBackupScheduleResponses]; export type UpdateBackupScheduleData = { - body?: { - cronExpression: string; - repositoryId: string; - enabled?: boolean; - excludeIfPresent?: Array; - excludePatterns?: Array; - includePatterns?: Array; - name?: string; - oneFileSystem?: boolean; - retentionPolicy?: { - keepDaily?: number; - keepHourly?: number; - keepLast?: number; - keepMonthly?: number; - keepWeekly?: number; - keepWithinDuration?: string; - keepYearly?: number; - }; - tags?: Array; - }; - path: { - scheduleId: string; - }; - query?: never; - url: '/api/v1/backups/{scheduleId}'; + body?: { + cronExpression: string; + repositoryId: string; + enabled?: boolean; + excludeIfPresent?: Array; + excludePatterns?: Array; + includePatterns?: Array; + name?: string; + oneFileSystem?: boolean; + retentionPolicy?: { + keepDaily?: number; + keepHourly?: number; + keepLast?: number; + keepMonthly?: number; + keepWeekly?: number; + keepWithinDuration?: string; + keepYearly?: number; + }; + tags?: Array; + }; + path: { + scheduleId: string; + }; + query?: never; + url: "/api/v1/backups/{scheduleId}"; }; export type UpdateBackupScheduleResponses = { - /** - * Backup schedule updated successfully - */ - 200: { - createdAt: number; - cronExpression: string; - enabled: boolean; - excludeIfPresent: Array | null; - excludePatterns: Array | null; - id: number; - includePatterns: Array | null; - lastBackupAt: number | null; - lastBackupError: string | null; - lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null; - name: string; - nextBackupAt: number | null; - oneFileSystem: boolean; - repositoryId: string; - retentionPolicy: { - keepDaily?: number; - keepHourly?: number; - keepLast?: number; - keepMonthly?: number; - keepWeekly?: number; - keepWithinDuration?: string; - keepYearly?: number; - } | null; - shortId: string; - updatedAt: number; - volumeId: number; - }; + /** + * Backup schedule updated successfully + */ + 200: { + createdAt: number; + cronExpression: string; + enabled: boolean; + excludeIfPresent: Array | null; + excludePatterns: Array | null; + id: number; + includePatterns: Array | null; + lastBackupAt: number | null; + lastBackupError: string | null; + lastBackupStatus: "error" | "in_progress" | "success" | "warning" | null; + name: string; + nextBackupAt: number | null; + oneFileSystem: boolean; + repositoryId: string; + retentionPolicy: { + keepDaily?: number; + keepHourly?: number; + keepLast?: number; + keepMonthly?: number; + keepWeekly?: number; + keepWithinDuration?: string; + keepYearly?: number; + } | null; + shortId: string; + updatedAt: number; + volumeId: number; + }; }; -export type UpdateBackupScheduleResponse = UpdateBackupScheduleResponses[keyof UpdateBackupScheduleResponses]; +export type UpdateBackupScheduleResponse = + UpdateBackupScheduleResponses[keyof UpdateBackupScheduleResponses]; export type GetBackupScheduleForVolumeData = { - body?: never; - path: { - volumeId: string; - }; - query?: never; - url: '/api/v1/backups/volume/{volumeId}'; + body?: never; + path: { + volumeId: string; + }; + query?: never; + url: "/api/v1/backups/volume/{volumeId}"; }; export type GetBackupScheduleForVolumeResponses = { - /** - * Backup schedule details for the volume - */ - 200: { - createdAt: number; - cronExpression: string; - enabled: boolean; - excludeIfPresent: Array | null; - excludePatterns: Array | null; - id: number; - includePatterns: Array | null; - lastBackupAt: number | null; - lastBackupError: string | null; - lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null; - name: string; - nextBackupAt: number | null; - oneFileSystem: boolean; - repository: { - compressionMode: 'auto' | 'max' | 'off' | null; - config: { - accessKeyId: string; - backend: 'r2'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accessKeyId: string; - backend: 's3'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accountKey: string; - accountName: string; - backend: 'azure'; - container: string; - cacert?: string; - customPassword?: string; - endpointSuffix?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'gcs'; - bucket: string; - credentialsJson: string; - projectId: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'local'; - name: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - path?: string; - } | { - backend: 'rclone'; - path: string; - remote: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'rest'; - url: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - password?: string; - path?: string; - username?: string; - } | { - backend: 'sftp'; - host: string; - path: string; - privateKey: string; - user: string; - port?: number; - skipHostKeyCheck?: boolean; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - knownHosts?: string; - }; - createdAt: number; - id: string; - lastChecked: number | null; - lastError: string | null; - name: string; - shortId: string; - status: 'error' | 'healthy' | 'unknown' | null; - type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp'; - updatedAt: number; - }; - repositoryId: string; - retentionPolicy: { - keepDaily?: number; - keepHourly?: number; - keepLast?: number; - keepMonthly?: number; - keepWeekly?: number; - keepWithinDuration?: string; - keepYearly?: number; - } | null; - shortId: string; - updatedAt: number; - volume: { - autoRemount: boolean; - config: { - backend: 'directory'; - path: string; - readOnly?: false; - } | { - backend: 'nfs'; - exportPath: string; - server: string; - version: '3' | '4' | '4.1'; - port?: number; - readOnly?: boolean; - } | { - backend: 'rclone'; - path: string; - remote: string; - readOnly?: boolean; - } | { - backend: 'sftp'; - host: string; - path: string; - username: string; - port?: number; - skipHostKeyCheck?: boolean; - knownHosts?: string; - password?: string; - privateKey?: string; - readOnly?: boolean; - } | { - backend: 'smb'; - password: string; - server: string; - share: string; - username: string; - vers?: '1.0' | '2.0' | '2.1' | '3.0'; - port?: number; - domain?: string; - readOnly?: boolean; - } | { - backend: 'webdav'; - path: string; - server: string; - port?: number; - password?: string; - readOnly?: boolean; - ssl?: boolean; - username?: string; - }; - createdAt: number; - id: number; - lastError: string | null; - lastHealthCheck: number; - name: string; - shortId: string; - status: 'error' | 'mounted' | 'unmounted'; - type: 'directory' | 'nfs' | 'rclone' | 'sftp' | 'smb' | 'webdav'; - updatedAt: number; - }; - volumeId: number; - } | null; + /** + * Backup schedule details for the volume + */ + 200: { + createdAt: number; + cronExpression: string; + enabled: boolean; + excludeIfPresent: Array | null; + excludePatterns: Array | null; + id: number; + includePatterns: Array | null; + lastBackupAt: number | null; + lastBackupError: string | null; + lastBackupStatus: "error" | "in_progress" | "success" | "warning" | null; + name: string; + nextBackupAt: number | null; + oneFileSystem: boolean; + repository: { + compressionMode: "auto" | "max" | "off" | null; + config: + | { + accessKeyId: string; + backend: "r2"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accessKeyId: string; + backend: "s3"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accountKey: string; + accountName: string; + backend: "azure"; + container: string; + cacert?: string; + customPassword?: string; + endpointSuffix?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "gcs"; + bucket: string; + credentialsJson: string; + projectId: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "local"; + name: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + path?: string; + } + | { + backend: "rclone"; + path: string; + remote: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "rest"; + url: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + password?: string; + path?: string; + username?: string; + } + | { + backend: "sftp"; + host: string; + path: string; + privateKey: string; + user: string; + port?: number; + skipHostKeyCheck?: boolean; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + knownHosts?: string; + }; + createdAt: number; + id: string; + lastChecked: number | null; + lastError: string | null; + name: string; + shortId: string; + status: "error" | "healthy" | "unknown" | null; + type: + | "azure" + | "gcs" + | "local" + | "r2" + | "rclone" + | "rest" + | "s3" + | "sftp"; + updatedAt: number; + }; + repositoryId: string; + retentionPolicy: { + keepDaily?: number; + keepHourly?: number; + keepLast?: number; + keepMonthly?: number; + keepWeekly?: number; + keepWithinDuration?: string; + keepYearly?: number; + } | null; + shortId: string; + updatedAt: number; + volume: { + autoRemount: boolean; + config: + | { + backend: "directory"; + path: string; + readOnly?: false; + } + | { + backend: "nfs"; + exportPath: string; + server: string; + version: "3" | "4" | "4.1"; + port?: number; + readOnly?: boolean; + } + | { + backend: "rclone"; + path: string; + remote: string; + readOnly?: boolean; + } + | { + backend: "sftp"; + host: string; + path: string; + username: string; + port?: number; + skipHostKeyCheck?: boolean; + knownHosts?: string; + password?: string; + privateKey?: string; + readOnly?: boolean; + } + | { + backend: "smb"; + password: string; + server: string; + share: string; + username: string; + vers?: "1.0" | "2.0" | "2.1" | "3.0"; + port?: number; + domain?: string; + readOnly?: boolean; + } + | { + backend: "webdav"; + path: string; + server: string; + port?: number; + password?: string; + readOnly?: boolean; + ssl?: boolean; + username?: string; + }; + createdAt: number; + id: number; + lastError: string | null; + lastHealthCheck: number; + name: string; + shortId: string; + status: "error" | "mounted" | "unmounted"; + type: "directory" | "nfs" | "rclone" | "sftp" | "smb" | "webdav"; + updatedAt: number; + }; + volumeId: number; + } | null; }; -export type GetBackupScheduleForVolumeResponse = GetBackupScheduleForVolumeResponses[keyof GetBackupScheduleForVolumeResponses]; +export type GetBackupScheduleForVolumeResponse = + GetBackupScheduleForVolumeResponses[keyof GetBackupScheduleForVolumeResponses]; export type RunBackupNowData = { - body?: never; - path: { - scheduleId: string; - }; - query?: never; - url: '/api/v1/backups/{scheduleId}/run'; + body?: never; + path: { + scheduleId: string; + }; + query?: never; + url: "/api/v1/backups/{scheduleId}/run"; }; export type RunBackupNowResponses = { - /** - * Backup started successfully - */ - 200: { - success: boolean; - }; + /** + * Backup started successfully + */ + 200: { + success: boolean; + }; }; -export type RunBackupNowResponse = RunBackupNowResponses[keyof RunBackupNowResponses]; +export type RunBackupNowResponse = + RunBackupNowResponses[keyof RunBackupNowResponses]; export type StopBackupData = { - body?: never; - path: { - scheduleId: string; - }; - query?: never; - url: '/api/v1/backups/{scheduleId}/stop'; + body?: never; + path: { + scheduleId: string; + }; + query?: never; + url: "/api/v1/backups/{scheduleId}/stop"; }; export type StopBackupErrors = { - /** - * No backup is currently running for this schedule - */ - 409: unknown; + /** + * No backup is currently running for this schedule + */ + 409: unknown; }; export type StopBackupResponses = { - /** - * Backup stopped successfully - */ - 200: { - success: boolean; - }; + /** + * Backup stopped successfully + */ + 200: { + success: boolean; + }; }; export type StopBackupResponse = StopBackupResponses[keyof StopBackupResponses]; export type RunForgetData = { - body?: never; - path: { - scheduleId: string; - }; - query?: never; - url: '/api/v1/backups/{scheduleId}/forget'; + body?: never; + path: { + scheduleId: string; + }; + query?: never; + url: "/api/v1/backups/{scheduleId}/forget"; }; export type RunForgetResponses = { - /** - * Retention policy applied successfully - */ - 200: { - success: boolean; - }; + /** + * Retention policy applied successfully + */ + 200: { + success: boolean; + }; }; export type RunForgetResponse = RunForgetResponses[keyof RunForgetResponses]; export type GetScheduleNotificationsData = { - body?: never; - path: { - scheduleId: string; - }; - query?: never; - url: '/api/v1/backups/{scheduleId}/notifications'; + body?: never; + path: { + scheduleId: string; + }; + query?: never; + url: "/api/v1/backups/{scheduleId}/notifications"; }; export type GetScheduleNotificationsResponses = { - /** - * List of notification assignments for the schedule - */ - 200: Array<{ - createdAt: number; - destination: { - config: { - apiToken: string; - priority: -1 | 0 | 1; - type: 'pushover'; - userKey: string; - devices?: string; - } | { - botToken: string; - chatId: string; - type: 'telegram'; - } | { - from: string; - smtpHost: string; - smtpPort: number; - to: Array; - type: 'email'; - useTLS: boolean; - password?: string; - username?: string; - } | { - method: 'GET' | 'POST'; - type: 'generic'; - url: string; - contentType?: string; - headers?: Array; - messageKey?: string; - titleKey?: string; - useJson?: boolean; - } | { - priority: 'default' | 'high' | 'low' | 'max' | 'min'; - topic: string; - type: 'ntfy'; - accessToken?: string; - password?: string; - serverUrl?: string; - username?: string; - } | { - priority: number; - serverUrl: string; - token: string; - type: 'gotify'; - path?: string; - } | { - shoutrrrUrl: string; - type: 'custom'; - } | { - type: 'discord'; - webhookUrl: string; - avatarUrl?: string; - threadId?: string; - username?: string; - } | { - type: 'slack'; - webhookUrl: string; - channel?: string; - iconEmoji?: string; - username?: string; - }; - createdAt: number; - enabled: boolean; - id: number; - name: string; - type: 'custom' | 'discord' | 'email' | 'generic' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram'; - updatedAt: number; - }; - destinationId: number; - notifyOnFailure: boolean; - notifyOnStart: boolean; - notifyOnSuccess: boolean; - notifyOnWarning: boolean; - scheduleId: number; - }>; + /** + * List of notification assignments for the schedule + */ + 200: Array<{ + createdAt: number; + destination: { + config: + | { + apiToken: string; + priority: -1 | 0 | 1; + type: "pushover"; + userKey: string; + devices?: string; + } + | { + botToken: string; + chatId: string; + type: "telegram"; + } + | { + from: string; + smtpHost: string; + smtpPort: number; + to: Array; + type: "email"; + useTLS: boolean; + password?: string; + username?: string; + } + | { + method: "GET" | "POST"; + type: "generic"; + url: string; + contentType?: string; + headers?: Array; + messageKey?: string; + titleKey?: string; + useJson?: boolean; + } + | { + priority: "default" | "high" | "low" | "max" | "min"; + topic: string; + type: "ntfy"; + accessToken?: string; + password?: string; + serverUrl?: string; + username?: string; + } + | { + priority: number; + serverUrl: string; + token: string; + type: "gotify"; + path?: string; + } + | { + shoutrrrUrl: string; + type: "custom"; + } + | { + type: "discord"; + webhookUrl: string; + avatarUrl?: string; + threadId?: string; + username?: string; + } + | { + type: "slack"; + webhookUrl: string; + channel?: string; + iconEmoji?: string; + username?: string; + }; + createdAt: number; + enabled: boolean; + id: number; + name: string; + type: + | "custom" + | "discord" + | "email" + | "generic" + | "gotify" + | "ntfy" + | "pushover" + | "slack" + | "telegram"; + updatedAt: number; + }; + destinationId: number; + notifyOnFailure: boolean; + notifyOnStart: boolean; + notifyOnSuccess: boolean; + notifyOnWarning: boolean; + scheduleId: number; + }>; }; -export type GetScheduleNotificationsResponse = GetScheduleNotificationsResponses[keyof GetScheduleNotificationsResponses]; +export type GetScheduleNotificationsResponse = + GetScheduleNotificationsResponses[keyof GetScheduleNotificationsResponses]; export type UpdateScheduleNotificationsData = { - body?: { - assignments: Array<{ - destinationId: number; - notifyOnFailure: boolean; - notifyOnStart: boolean; - notifyOnSuccess: boolean; - notifyOnWarning: boolean; - }>; - }; - path: { - scheduleId: string; - }; - query?: never; - url: '/api/v1/backups/{scheduleId}/notifications'; + body?: { + assignments: Array<{ + destinationId: number; + notifyOnFailure: boolean; + notifyOnStart: boolean; + notifyOnSuccess: boolean; + notifyOnWarning: boolean; + }>; + }; + path: { + scheduleId: string; + }; + query?: never; + url: "/api/v1/backups/{scheduleId}/notifications"; }; export type UpdateScheduleNotificationsResponses = { - /** - * Notification assignments updated successfully - */ - 200: Array<{ - createdAt: number; - destination: { - config: { - apiToken: string; - priority: -1 | 0 | 1; - type: 'pushover'; - userKey: string; - devices?: string; - } | { - botToken: string; - chatId: string; - type: 'telegram'; - } | { - from: string; - smtpHost: string; - smtpPort: number; - to: Array; - type: 'email'; - useTLS: boolean; - password?: string; - username?: string; - } | { - method: 'GET' | 'POST'; - type: 'generic'; - url: string; - contentType?: string; - headers?: Array; - messageKey?: string; - titleKey?: string; - useJson?: boolean; - } | { - priority: 'default' | 'high' | 'low' | 'max' | 'min'; - topic: string; - type: 'ntfy'; - accessToken?: string; - password?: string; - serverUrl?: string; - username?: string; - } | { - priority: number; - serverUrl: string; - token: string; - type: 'gotify'; - path?: string; - } | { - shoutrrrUrl: string; - type: 'custom'; - } | { - type: 'discord'; - webhookUrl: string; - avatarUrl?: string; - threadId?: string; - username?: string; - } | { - type: 'slack'; - webhookUrl: string; - channel?: string; - iconEmoji?: string; - username?: string; - }; - createdAt: number; - enabled: boolean; - id: number; - name: string; - type: 'custom' | 'discord' | 'email' | 'generic' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram'; - updatedAt: number; - }; - destinationId: number; - notifyOnFailure: boolean; - notifyOnStart: boolean; - notifyOnSuccess: boolean; - notifyOnWarning: boolean; - scheduleId: number; - }>; + /** + * Notification assignments updated successfully + */ + 200: Array<{ + createdAt: number; + destination: { + config: + | { + apiToken: string; + priority: -1 | 0 | 1; + type: "pushover"; + userKey: string; + devices?: string; + } + | { + botToken: string; + chatId: string; + type: "telegram"; + } + | { + from: string; + smtpHost: string; + smtpPort: number; + to: Array; + type: "email"; + useTLS: boolean; + password?: string; + username?: string; + } + | { + method: "GET" | "POST"; + type: "generic"; + url: string; + contentType?: string; + headers?: Array; + messageKey?: string; + titleKey?: string; + useJson?: boolean; + } + | { + priority: "default" | "high" | "low" | "max" | "min"; + topic: string; + type: "ntfy"; + accessToken?: string; + password?: string; + serverUrl?: string; + username?: string; + } + | { + priority: number; + serverUrl: string; + token: string; + type: "gotify"; + path?: string; + } + | { + shoutrrrUrl: string; + type: "custom"; + } + | { + type: "discord"; + webhookUrl: string; + avatarUrl?: string; + threadId?: string; + username?: string; + } + | { + type: "slack"; + webhookUrl: string; + channel?: string; + iconEmoji?: string; + username?: string; + }; + createdAt: number; + enabled: boolean; + id: number; + name: string; + type: + | "custom" + | "discord" + | "email" + | "generic" + | "gotify" + | "ntfy" + | "pushover" + | "slack" + | "telegram"; + updatedAt: number; + }; + destinationId: number; + notifyOnFailure: boolean; + notifyOnStart: boolean; + notifyOnSuccess: boolean; + notifyOnWarning: boolean; + scheduleId: number; + }>; }; -export type UpdateScheduleNotificationsResponse = UpdateScheduleNotificationsResponses[keyof UpdateScheduleNotificationsResponses]; +export type UpdateScheduleNotificationsResponse = + UpdateScheduleNotificationsResponses[keyof UpdateScheduleNotificationsResponses]; export type GetScheduleMirrorsData = { - body?: never; - path: { - scheduleId: string; - }; - query?: never; - url: '/api/v1/backups/{scheduleId}/mirrors'; + body?: never; + path: { + scheduleId: string; + }; + query?: never; + url: "/api/v1/backups/{scheduleId}/mirrors"; }; export type GetScheduleMirrorsResponses = { - /** - * List of mirror repository assignments for the schedule - */ - 200: Array<{ - createdAt: number; - enabled: boolean; - lastCopyAt: number | null; - lastCopyError: string | null; - lastCopyStatus: 'error' | 'success' | null; - repository: { - compressionMode: 'auto' | 'max' | 'off' | null; - config: { - accessKeyId: string; - backend: 'r2'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accessKeyId: string; - backend: 's3'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accountKey: string; - accountName: string; - backend: 'azure'; - container: string; - cacert?: string; - customPassword?: string; - endpointSuffix?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'gcs'; - bucket: string; - credentialsJson: string; - projectId: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'local'; - name: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - path?: string; - } | { - backend: 'rclone'; - path: string; - remote: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'rest'; - url: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - password?: string; - path?: string; - username?: string; - } | { - backend: 'sftp'; - host: string; - path: string; - privateKey: string; - user: string; - port?: number; - skipHostKeyCheck?: boolean; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - knownHosts?: string; - }; - createdAt: number; - id: string; - lastChecked: number | null; - lastError: string | null; - name: string; - shortId: string; - status: 'error' | 'healthy' | 'unknown' | null; - type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp'; - updatedAt: number; - }; - repositoryId: string; - scheduleId: number; - }>; + /** + * List of mirror repository assignments for the schedule + */ + 200: Array<{ + createdAt: number; + enabled: boolean; + lastCopyAt: number | null; + lastCopyError: string | null; + lastCopyStatus: "error" | "success" | null; + repository: { + compressionMode: "auto" | "max" | "off" | null; + config: + | { + accessKeyId: string; + backend: "r2"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accessKeyId: string; + backend: "s3"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accountKey: string; + accountName: string; + backend: "azure"; + container: string; + cacert?: string; + customPassword?: string; + endpointSuffix?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "gcs"; + bucket: string; + credentialsJson: string; + projectId: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "local"; + name: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + path?: string; + } + | { + backend: "rclone"; + path: string; + remote: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "rest"; + url: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + password?: string; + path?: string; + username?: string; + } + | { + backend: "sftp"; + host: string; + path: string; + privateKey: string; + user: string; + port?: number; + skipHostKeyCheck?: boolean; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + knownHosts?: string; + }; + createdAt: number; + id: string; + lastChecked: number | null; + lastError: string | null; + name: string; + shortId: string; + status: "error" | "healthy" | "unknown" | null; + type: + | "azure" + | "gcs" + | "local" + | "r2" + | "rclone" + | "rest" + | "s3" + | "sftp"; + updatedAt: number; + }; + repositoryId: string; + scheduleId: number; + }>; }; -export type GetScheduleMirrorsResponse = GetScheduleMirrorsResponses[keyof GetScheduleMirrorsResponses]; +export type GetScheduleMirrorsResponse = + GetScheduleMirrorsResponses[keyof GetScheduleMirrorsResponses]; export type UpdateScheduleMirrorsData = { - body?: { - mirrors: Array<{ - enabled: boolean; - repositoryId: string; - }>; - }; - path: { - scheduleId: string; - }; - query?: never; - url: '/api/v1/backups/{scheduleId}/mirrors'; + body?: { + mirrors: Array<{ + enabled: boolean; + repositoryId: string; + }>; + }; + path: { + scheduleId: string; + }; + query?: never; + url: "/api/v1/backups/{scheduleId}/mirrors"; }; export type UpdateScheduleMirrorsResponses = { - /** - * Mirror assignments updated successfully - */ - 200: Array<{ - createdAt: number; - enabled: boolean; - lastCopyAt: number | null; - lastCopyError: string | null; - lastCopyStatus: 'error' | 'success' | null; - repository: { - compressionMode: 'auto' | 'max' | 'off' | null; - config: { - accessKeyId: string; - backend: 'r2'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accessKeyId: string; - backend: 's3'; - bucket: string; - endpoint: string; - secretAccessKey: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - accountKey: string; - accountName: string; - backend: 'azure'; - container: string; - cacert?: string; - customPassword?: string; - endpointSuffix?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'gcs'; - bucket: string; - credentialsJson: string; - projectId: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'local'; - name: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - path?: string; - } | { - backend: 'rclone'; - path: string; - remote: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - } | { - backend: 'rest'; - url: string; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - password?: string; - path?: string; - username?: string; - } | { - backend: 'sftp'; - host: string; - path: string; - privateKey: string; - user: string; - port?: number; - skipHostKeyCheck?: boolean; - cacert?: string; - customPassword?: string; - insecureTls?: boolean; - isExistingRepository?: boolean; - knownHosts?: string; - }; - createdAt: number; - id: string; - lastChecked: number | null; - lastError: string | null; - name: string; - shortId: string; - status: 'error' | 'healthy' | 'unknown' | null; - type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3' | 'sftp'; - updatedAt: number; - }; - repositoryId: string; - scheduleId: number; - }>; + /** + * Mirror assignments updated successfully + */ + 200: Array<{ + createdAt: number; + enabled: boolean; + lastCopyAt: number | null; + lastCopyError: string | null; + lastCopyStatus: "error" | "success" | null; + repository: { + compressionMode: "auto" | "max" | "off" | null; + config: + | { + accessKeyId: string; + backend: "r2"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accessKeyId: string; + backend: "s3"; + bucket: string; + endpoint: string; + secretAccessKey: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + accountKey: string; + accountName: string; + backend: "azure"; + container: string; + cacert?: string; + customPassword?: string; + endpointSuffix?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "gcs"; + bucket: string; + credentialsJson: string; + projectId: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "local"; + name: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + path?: string; + } + | { + backend: "rclone"; + path: string; + remote: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + } + | { + backend: "rest"; + url: string; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + password?: string; + path?: string; + username?: string; + } + | { + backend: "sftp"; + host: string; + path: string; + privateKey: string; + user: string; + port?: number; + skipHostKeyCheck?: boolean; + cacert?: string; + customPassword?: string; + insecureTls?: boolean; + isExistingRepository?: boolean; + knownHosts?: string; + }; + createdAt: number; + id: string; + lastChecked: number | null; + lastError: string | null; + name: string; + shortId: string; + status: "error" | "healthy" | "unknown" | null; + type: + | "azure" + | "gcs" + | "local" + | "r2" + | "rclone" + | "rest" + | "s3" + | "sftp"; + updatedAt: number; + }; + repositoryId: string; + scheduleId: number; + }>; }; -export type UpdateScheduleMirrorsResponse = UpdateScheduleMirrorsResponses[keyof UpdateScheduleMirrorsResponses]; +export type UpdateScheduleMirrorsResponse = + UpdateScheduleMirrorsResponses[keyof UpdateScheduleMirrorsResponses]; export type GetMirrorCompatibilityData = { - body?: never; - path: { - scheduleId: string; - }; - query?: never; - url: '/api/v1/backups/{scheduleId}/mirrors/compatibility'; + body?: never; + path: { + scheduleId: string; + }; + query?: never; + url: "/api/v1/backups/{scheduleId}/mirrors/compatibility"; }; export type GetMirrorCompatibilityResponses = { - /** - * List of repositories with their mirror compatibility status - */ - 200: Array<{ - compatible: boolean; - reason: string | null; - repositoryId: string; - }>; + /** + * List of repositories with their mirror compatibility status + */ + 200: Array<{ + compatible: boolean; + reason: string | null; + repositoryId: string; + }>; }; -export type GetMirrorCompatibilityResponse = GetMirrorCompatibilityResponses[keyof GetMirrorCompatibilityResponses]; +export type GetMirrorCompatibilityResponse = + GetMirrorCompatibilityResponses[keyof GetMirrorCompatibilityResponses]; export type ReorderBackupSchedulesData = { - body?: { - scheduleIds: Array; - }; - path?: never; - query?: never; - url: '/api/v1/backups/reorder'; + body?: { + scheduleIds: Array; + }; + path?: never; + query?: never; + url: "/api/v1/backups/reorder"; }; export type ReorderBackupSchedulesResponses = { - /** - * Backup schedules reordered successfully - */ - 200: { - success: boolean; - }; + /** + * Backup schedules reordered successfully + */ + 200: { + success: boolean; + }; }; -export type ReorderBackupSchedulesResponse = ReorderBackupSchedulesResponses[keyof ReorderBackupSchedulesResponses]; +export type ReorderBackupSchedulesResponse = + ReorderBackupSchedulesResponses[keyof ReorderBackupSchedulesResponses]; export type ListNotificationDestinationsData = { - body?: never; - path?: never; - query?: never; - url: '/api/v1/notifications/destinations'; + body?: never; + path?: never; + query?: never; + url: "/api/v1/notifications/destinations"; }; export type ListNotificationDestinationsResponses = { - /** - * A list of notification destinations - */ - 200: Array<{ - config: { - apiToken: string; - priority: -1 | 0 | 1; - type: 'pushover'; - userKey: string; - devices?: string; - } | { - botToken: string; - chatId: string; - type: 'telegram'; - } | { - from: string; - smtpHost: string; - smtpPort: number; - to: Array; - type: 'email'; - useTLS: boolean; - password?: string; - username?: string; - } | { - method: 'GET' | 'POST'; - type: 'generic'; - url: string; - contentType?: string; - headers?: Array; - messageKey?: string; - titleKey?: string; - useJson?: boolean; - } | { - priority: 'default' | 'high' | 'low' | 'max' | 'min'; - topic: string; - type: 'ntfy'; - accessToken?: string; - password?: string; - serverUrl?: string; - username?: string; - } | { - priority: number; - serverUrl: string; - token: string; - type: 'gotify'; - path?: string; - } | { - shoutrrrUrl: string; - type: 'custom'; - } | { - type: 'discord'; - webhookUrl: string; - avatarUrl?: string; - threadId?: string; - username?: string; - } | { - type: 'slack'; - webhookUrl: string; - channel?: string; - iconEmoji?: string; - username?: string; - }; - createdAt: number; - enabled: boolean; - id: number; - name: string; - type: 'custom' | 'discord' | 'email' | 'generic' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram'; - updatedAt: number; - }>; + /** + * A list of notification destinations + */ + 200: Array<{ + config: + | { + apiToken: string; + priority: -1 | 0 | 1; + type: "pushover"; + userKey: string; + devices?: string; + } + | { + botToken: string; + chatId: string; + type: "telegram"; + } + | { + from: string; + smtpHost: string; + smtpPort: number; + to: Array; + type: "email"; + useTLS: boolean; + password?: string; + username?: string; + } + | { + method: "GET" | "POST"; + type: "generic"; + url: string; + contentType?: string; + headers?: Array; + messageKey?: string; + titleKey?: string; + useJson?: boolean; + } + | { + priority: "default" | "high" | "low" | "max" | "min"; + topic: string; + type: "ntfy"; + accessToken?: string; + password?: string; + serverUrl?: string; + username?: string; + } + | { + priority: number; + serverUrl: string; + token: string; + type: "gotify"; + path?: string; + } + | { + shoutrrrUrl: string; + type: "custom"; + } + | { + type: "discord"; + webhookUrl: string; + avatarUrl?: string; + threadId?: string; + username?: string; + } + | { + type: "slack"; + webhookUrl: string; + channel?: string; + iconEmoji?: string; + username?: string; + }; + createdAt: number; + enabled: boolean; + id: number; + name: string; + type: + | "custom" + | "discord" + | "email" + | "generic" + | "gotify" + | "ntfy" + | "pushover" + | "slack" + | "telegram"; + updatedAt: number; + }>; }; -export type ListNotificationDestinationsResponse = ListNotificationDestinationsResponses[keyof ListNotificationDestinationsResponses]; +export type ListNotificationDestinationsResponse = + ListNotificationDestinationsResponses[keyof ListNotificationDestinationsResponses]; export type CreateNotificationDestinationData = { - body?: { - config: { - apiToken: string; - priority: -1 | 0 | 1; - type: 'pushover'; - userKey: string; - devices?: string; - } | { - botToken: string; - chatId: string; - type: 'telegram'; - } | { - from: string; - smtpHost: string; - smtpPort: number; - to: Array; - type: 'email'; - useTLS: boolean; - password?: string; - username?: string; - } | { - method: 'GET' | 'POST'; - type: 'generic'; - url: string; - contentType?: string; - headers?: Array; - messageKey?: string; - titleKey?: string; - useJson?: boolean; - } | { - priority: 'default' | 'high' | 'low' | 'max' | 'min'; - topic: string; - type: 'ntfy'; - accessToken?: string; - password?: string; - serverUrl?: string; - username?: string; - } | { - priority: number; - serverUrl: string; - token: string; - type: 'gotify'; - path?: string; - } | { - shoutrrrUrl: string; - type: 'custom'; - } | { - type: 'discord'; - webhookUrl: string; - avatarUrl?: string; - threadId?: string; - username?: string; - } | { - type: 'slack'; - webhookUrl: string; - channel?: string; - iconEmoji?: string; - username?: string; - }; - name: string; - }; - path?: never; - query?: never; - url: '/api/v1/notifications/destinations'; + body?: { + config: + | { + apiToken: string; + priority: -1 | 0 | 1; + type: "pushover"; + userKey: string; + devices?: string; + } + | { + botToken: string; + chatId: string; + type: "telegram"; + } + | { + from: string; + smtpHost: string; + smtpPort: number; + to: Array; + type: "email"; + useTLS: boolean; + password?: string; + username?: string; + } + | { + method: "GET" | "POST"; + type: "generic"; + url: string; + contentType?: string; + headers?: Array; + messageKey?: string; + titleKey?: string; + useJson?: boolean; + } + | { + priority: "default" | "high" | "low" | "max" | "min"; + topic: string; + type: "ntfy"; + accessToken?: string; + password?: string; + serverUrl?: string; + username?: string; + } + | { + priority: number; + serverUrl: string; + token: string; + type: "gotify"; + path?: string; + } + | { + shoutrrrUrl: string; + type: "custom"; + } + | { + type: "discord"; + webhookUrl: string; + avatarUrl?: string; + threadId?: string; + username?: string; + } + | { + type: "slack"; + webhookUrl: string; + channel?: string; + iconEmoji?: string; + username?: string; + }; + name: string; + }; + path?: never; + query?: never; + url: "/api/v1/notifications/destinations"; }; export type CreateNotificationDestinationResponses = { - /** - * Notification destination created successfully - */ - 201: { - config: { - apiToken: string; - priority: -1 | 0 | 1; - type: 'pushover'; - userKey: string; - devices?: string; - } | { - botToken: string; - chatId: string; - type: 'telegram'; - } | { - from: string; - smtpHost: string; - smtpPort: number; - to: Array; - type: 'email'; - useTLS: boolean; - password?: string; - username?: string; - } | { - method: 'GET' | 'POST'; - type: 'generic'; - url: string; - contentType?: string; - headers?: Array; - messageKey?: string; - titleKey?: string; - useJson?: boolean; - } | { - priority: 'default' | 'high' | 'low' | 'max' | 'min'; - topic: string; - type: 'ntfy'; - accessToken?: string; - password?: string; - serverUrl?: string; - username?: string; - } | { - priority: number; - serverUrl: string; - token: string; - type: 'gotify'; - path?: string; - } | { - shoutrrrUrl: string; - type: 'custom'; - } | { - type: 'discord'; - webhookUrl: string; - avatarUrl?: string; - threadId?: string; - username?: string; - } | { - type: 'slack'; - webhookUrl: string; - channel?: string; - iconEmoji?: string; - username?: string; - }; - createdAt: number; - enabled: boolean; - id: number; - name: string; - type: 'custom' | 'discord' | 'email' | 'generic' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram'; - updatedAt: number; - }; + /** + * Notification destination created successfully + */ + 201: { + config: + | { + apiToken: string; + priority: -1 | 0 | 1; + type: "pushover"; + userKey: string; + devices?: string; + } + | { + botToken: string; + chatId: string; + type: "telegram"; + } + | { + from: string; + smtpHost: string; + smtpPort: number; + to: Array; + type: "email"; + useTLS: boolean; + password?: string; + username?: string; + } + | { + method: "GET" | "POST"; + type: "generic"; + url: string; + contentType?: string; + headers?: Array; + messageKey?: string; + titleKey?: string; + useJson?: boolean; + } + | { + priority: "default" | "high" | "low" | "max" | "min"; + topic: string; + type: "ntfy"; + accessToken?: string; + password?: string; + serverUrl?: string; + username?: string; + } + | { + priority: number; + serverUrl: string; + token: string; + type: "gotify"; + path?: string; + } + | { + shoutrrrUrl: string; + type: "custom"; + } + | { + type: "discord"; + webhookUrl: string; + avatarUrl?: string; + threadId?: string; + username?: string; + } + | { + type: "slack"; + webhookUrl: string; + channel?: string; + iconEmoji?: string; + username?: string; + }; + createdAt: number; + enabled: boolean; + id: number; + name: string; + type: + | "custom" + | "discord" + | "email" + | "generic" + | "gotify" + | "ntfy" + | "pushover" + | "slack" + | "telegram"; + updatedAt: number; + }; }; -export type CreateNotificationDestinationResponse = CreateNotificationDestinationResponses[keyof CreateNotificationDestinationResponses]; +export type CreateNotificationDestinationResponse = + CreateNotificationDestinationResponses[keyof CreateNotificationDestinationResponses]; export type DeleteNotificationDestinationData = { - body?: never; - path: { - id: string; - }; - query?: never; - url: '/api/v1/notifications/destinations/{id}'; + body?: never; + path: { + id: string; + }; + query?: never; + url: "/api/v1/notifications/destinations/{id}"; }; export type DeleteNotificationDestinationErrors = { - /** - * Notification destination not found - */ - 404: unknown; + /** + * Notification destination not found + */ + 404: unknown; }; export type DeleteNotificationDestinationResponses = { - /** - * Notification destination deleted successfully - */ - 200: { - message: string; - }; + /** + * Notification destination deleted successfully + */ + 200: { + message: string; + }; }; -export type DeleteNotificationDestinationResponse = DeleteNotificationDestinationResponses[keyof DeleteNotificationDestinationResponses]; +export type DeleteNotificationDestinationResponse = + DeleteNotificationDestinationResponses[keyof DeleteNotificationDestinationResponses]; export type GetNotificationDestinationData = { - body?: never; - path: { - id: string; - }; - query?: never; - url: '/api/v1/notifications/destinations/{id}'; + body?: never; + path: { + id: string; + }; + query?: never; + url: "/api/v1/notifications/destinations/{id}"; }; export type GetNotificationDestinationErrors = { - /** - * Notification destination not found - */ - 404: unknown; + /** + * Notification destination not found + */ + 404: unknown; }; export type GetNotificationDestinationResponses = { - /** - * Notification destination details - */ - 200: { - config: { - apiToken: string; - priority: -1 | 0 | 1; - type: 'pushover'; - userKey: string; - devices?: string; - } | { - botToken: string; - chatId: string; - type: 'telegram'; - } | { - from: string; - smtpHost: string; - smtpPort: number; - to: Array; - type: 'email'; - useTLS: boolean; - password?: string; - username?: string; - } | { - method: 'GET' | 'POST'; - type: 'generic'; - url: string; - contentType?: string; - headers?: Array; - messageKey?: string; - titleKey?: string; - useJson?: boolean; - } | { - priority: 'default' | 'high' | 'low' | 'max' | 'min'; - topic: string; - type: 'ntfy'; - accessToken?: string; - password?: string; - serverUrl?: string; - username?: string; - } | { - priority: number; - serverUrl: string; - token: string; - type: 'gotify'; - path?: string; - } | { - shoutrrrUrl: string; - type: 'custom'; - } | { - type: 'discord'; - webhookUrl: string; - avatarUrl?: string; - threadId?: string; - username?: string; - } | { - type: 'slack'; - webhookUrl: string; - channel?: string; - iconEmoji?: string; - username?: string; - }; - createdAt: number; - enabled: boolean; - id: number; - name: string; - type: 'custom' | 'discord' | 'email' | 'generic' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram'; - updatedAt: number; - }; + /** + * Notification destination details + */ + 200: { + config: + | { + apiToken: string; + priority: -1 | 0 | 1; + type: "pushover"; + userKey: string; + devices?: string; + } + | { + botToken: string; + chatId: string; + type: "telegram"; + } + | { + from: string; + smtpHost: string; + smtpPort: number; + to: Array; + type: "email"; + useTLS: boolean; + password?: string; + username?: string; + } + | { + method: "GET" | "POST"; + type: "generic"; + url: string; + contentType?: string; + headers?: Array; + messageKey?: string; + titleKey?: string; + useJson?: boolean; + } + | { + priority: "default" | "high" | "low" | "max" | "min"; + topic: string; + type: "ntfy"; + accessToken?: string; + password?: string; + serverUrl?: string; + username?: string; + } + | { + priority: number; + serverUrl: string; + token: string; + type: "gotify"; + path?: string; + } + | { + shoutrrrUrl: string; + type: "custom"; + } + | { + type: "discord"; + webhookUrl: string; + avatarUrl?: string; + threadId?: string; + username?: string; + } + | { + type: "slack"; + webhookUrl: string; + channel?: string; + iconEmoji?: string; + username?: string; + }; + createdAt: number; + enabled: boolean; + id: number; + name: string; + type: + | "custom" + | "discord" + | "email" + | "generic" + | "gotify" + | "ntfy" + | "pushover" + | "slack" + | "telegram"; + updatedAt: number; + }; }; -export type GetNotificationDestinationResponse = GetNotificationDestinationResponses[keyof GetNotificationDestinationResponses]; +export type GetNotificationDestinationResponse = + GetNotificationDestinationResponses[keyof GetNotificationDestinationResponses]; export type UpdateNotificationDestinationData = { - body?: { - config?: { - apiToken: string; - priority: -1 | 0 | 1; - type: 'pushover'; - userKey: string; - devices?: string; - } | { - botToken: string; - chatId: string; - type: 'telegram'; - } | { - from: string; - smtpHost: string; - smtpPort: number; - to: Array; - type: 'email'; - useTLS: boolean; - password?: string; - username?: string; - } | { - method: 'GET' | 'POST'; - type: 'generic'; - url: string; - contentType?: string; - headers?: Array; - messageKey?: string; - titleKey?: string; - useJson?: boolean; - } | { - priority: 'default' | 'high' | 'low' | 'max' | 'min'; - topic: string; - type: 'ntfy'; - accessToken?: string; - password?: string; - serverUrl?: string; - username?: string; - } | { - priority: number; - serverUrl: string; - token: string; - type: 'gotify'; - path?: string; - } | { - shoutrrrUrl: string; - type: 'custom'; - } | { - type: 'discord'; - webhookUrl: string; - avatarUrl?: string; - threadId?: string; - username?: string; - } | { - type: 'slack'; - webhookUrl: string; - channel?: string; - iconEmoji?: string; - username?: string; - }; - enabled?: boolean; - name?: string; - }; - path: { - id: string; - }; - query?: never; - url: '/api/v1/notifications/destinations/{id}'; + body?: { + config?: + | { + apiToken: string; + priority: -1 | 0 | 1; + type: "pushover"; + userKey: string; + devices?: string; + } + | { + botToken: string; + chatId: string; + type: "telegram"; + } + | { + from: string; + smtpHost: string; + smtpPort: number; + to: Array; + type: "email"; + useTLS: boolean; + password?: string; + username?: string; + } + | { + method: "GET" | "POST"; + type: "generic"; + url: string; + contentType?: string; + headers?: Array; + messageKey?: string; + titleKey?: string; + useJson?: boolean; + } + | { + priority: "default" | "high" | "low" | "max" | "min"; + topic: string; + type: "ntfy"; + accessToken?: string; + password?: string; + serverUrl?: string; + username?: string; + } + | { + priority: number; + serverUrl: string; + token: string; + type: "gotify"; + path?: string; + } + | { + shoutrrrUrl: string; + type: "custom"; + } + | { + type: "discord"; + webhookUrl: string; + avatarUrl?: string; + threadId?: string; + username?: string; + } + | { + type: "slack"; + webhookUrl: string; + channel?: string; + iconEmoji?: string; + username?: string; + }; + enabled?: boolean; + name?: string; + }; + path: { + id: string; + }; + query?: never; + url: "/api/v1/notifications/destinations/{id}"; }; export type UpdateNotificationDestinationErrors = { - /** - * Notification destination not found - */ - 404: unknown; + /** + * Notification destination not found + */ + 404: unknown; }; export type UpdateNotificationDestinationResponses = { - /** - * Notification destination updated successfully - */ - 200: { - config: { - apiToken: string; - priority: -1 | 0 | 1; - type: 'pushover'; - userKey: string; - devices?: string; - } | { - botToken: string; - chatId: string; - type: 'telegram'; - } | { - from: string; - smtpHost: string; - smtpPort: number; - to: Array; - type: 'email'; - useTLS: boolean; - password?: string; - username?: string; - } | { - method: 'GET' | 'POST'; - type: 'generic'; - url: string; - contentType?: string; - headers?: Array; - messageKey?: string; - titleKey?: string; - useJson?: boolean; - } | { - priority: 'default' | 'high' | 'low' | 'max' | 'min'; - topic: string; - type: 'ntfy'; - accessToken?: string; - password?: string; - serverUrl?: string; - username?: string; - } | { - priority: number; - serverUrl: string; - token: string; - type: 'gotify'; - path?: string; - } | { - shoutrrrUrl: string; - type: 'custom'; - } | { - type: 'discord'; - webhookUrl: string; - avatarUrl?: string; - threadId?: string; - username?: string; - } | { - type: 'slack'; - webhookUrl: string; - channel?: string; - iconEmoji?: string; - username?: string; - }; - createdAt: number; - enabled: boolean; - id: number; - name: string; - type: 'custom' | 'discord' | 'email' | 'generic' | 'gotify' | 'ntfy' | 'pushover' | 'slack' | 'telegram'; - updatedAt: number; - }; + /** + * Notification destination updated successfully + */ + 200: { + config: + | { + apiToken: string; + priority: -1 | 0 | 1; + type: "pushover"; + userKey: string; + devices?: string; + } + | { + botToken: string; + chatId: string; + type: "telegram"; + } + | { + from: string; + smtpHost: string; + smtpPort: number; + to: Array; + type: "email"; + useTLS: boolean; + password?: string; + username?: string; + } + | { + method: "GET" | "POST"; + type: "generic"; + url: string; + contentType?: string; + headers?: Array; + messageKey?: string; + titleKey?: string; + useJson?: boolean; + } + | { + priority: "default" | "high" | "low" | "max" | "min"; + topic: string; + type: "ntfy"; + accessToken?: string; + password?: string; + serverUrl?: string; + username?: string; + } + | { + priority: number; + serverUrl: string; + token: string; + type: "gotify"; + path?: string; + } + | { + shoutrrrUrl: string; + type: "custom"; + } + | { + type: "discord"; + webhookUrl: string; + avatarUrl?: string; + threadId?: string; + username?: string; + } + | { + type: "slack"; + webhookUrl: string; + channel?: string; + iconEmoji?: string; + username?: string; + }; + createdAt: number; + enabled: boolean; + id: number; + name: string; + type: + | "custom" + | "discord" + | "email" + | "generic" + | "gotify" + | "ntfy" + | "pushover" + | "slack" + | "telegram"; + updatedAt: number; + }; }; -export type UpdateNotificationDestinationResponse = UpdateNotificationDestinationResponses[keyof UpdateNotificationDestinationResponses]; +export type UpdateNotificationDestinationResponse = + UpdateNotificationDestinationResponses[keyof UpdateNotificationDestinationResponses]; export type TestNotificationDestinationData = { - body?: never; - path: { - id: string; - }; - query?: never; - url: '/api/v1/notifications/destinations/{id}/test'; + body?: never; + path: { + id: string; + }; + query?: never; + url: "/api/v1/notifications/destinations/{id}/test"; }; export type TestNotificationDestinationErrors = { - /** - * Notification destination not found - */ - 404: unknown; - /** - * Cannot test disabled destination - */ - 409: unknown; - /** - * Failed to send test notification - */ - 500: unknown; + /** + * Notification destination not found + */ + 404: unknown; + /** + * Cannot test disabled destination + */ + 409: unknown; + /** + * Failed to send test notification + */ + 500: unknown; }; export type TestNotificationDestinationResponses = { - /** - * Test notification sent successfully - */ - 200: { - success: boolean; - }; + /** + * Test notification sent successfully + */ + 200: { + success: boolean; + }; }; -export type TestNotificationDestinationResponse = TestNotificationDestinationResponses[keyof TestNotificationDestinationResponses]; +export type TestNotificationDestinationResponse = + TestNotificationDestinationResponses[keyof TestNotificationDestinationResponses]; export type GetSystemInfoData = { - body?: never; - path?: never; - query?: never; - url: '/api/v1/system/info'; + body?: never; + path?: never; + query?: never; + url: "/api/v1/system/info"; }; export type GetSystemInfoResponses = { - /** - * System information with enabled capabilities - */ - 200: { - capabilities: { - rclone: boolean; - sysAdmin: boolean; - }; - }; + /** + * System information with enabled capabilities + */ + 200: { + capabilities: { + rclone: boolean; + sysAdmin: boolean; + }; + }; }; -export type GetSystemInfoResponse = GetSystemInfoResponses[keyof GetSystemInfoResponses]; +export type GetSystemInfoResponse = + GetSystemInfoResponses[keyof GetSystemInfoResponses]; export type GetUpdatesData = { - body?: never; - path?: never; - query?: never; - url: '/api/v1/system/updates'; + body?: never; + path?: never; + query?: never; + url: "/api/v1/system/updates"; }; export type GetUpdatesResponses = { - /** - * Update information and missed releases - */ - 200: { - currentVersion: string; - hasUpdate: boolean; - latestVersion: string; - missedReleases: Array<{ - body: string; - publishedAt: string; - url: string; - version: string; - }>; - }; + /** + * Update information and missed releases + */ + 200: { + currentVersion: string; + hasUpdate: boolean; + latestVersion: string; + missedReleases: Array<{ + body: string; + publishedAt: string; + url: string; + version: string; + }>; + }; }; export type GetUpdatesResponse = GetUpdatesResponses[keyof GetUpdatesResponses]; export type DownloadResticPasswordData = { - body?: { - password: string; - }; - path?: never; - query?: never; - url: '/api/v1/system/restic-password'; + body?: { + password: string; + }; + path?: never; + query?: never; + url: "/api/v1/system/restic-password"; }; export type DownloadResticPasswordResponses = { - /** - * Restic password file content - */ - 200: string; + /** + * Restic password file content + */ + 200: string; }; -export type DownloadResticPasswordResponse = DownloadResticPasswordResponses[keyof DownloadResticPasswordResponses]; +export type DownloadResticPasswordResponse = + DownloadResticPasswordResponses[keyof DownloadResticPasswordResponses]; diff --git a/app/client/components/layout.tsx b/app/client/components/layout.tsx index 3868bd9e..49e6a0b0 100644 --- a/app/client/components/layout.tsx +++ b/app/client/components/layout.tsx @@ -1,4 +1,3 @@ -import { useMutation } from "@tanstack/react-query"; import { LifeBuoy } from "lucide-react"; import { Outlet, redirect, useNavigate } from "react-router"; import { toast } from "sonner"; @@ -10,7 +9,7 @@ import { GridBackground } from "./grid-background"; import { Button } from "./ui/button"; import { SidebarProvider, SidebarTrigger } from "./ui/sidebar"; import { AppSidebar } from "./app-sidebar"; -import { logoutMutation } from "../api-client/@tanstack/react-query.gen"; +import { authClient } from "../lib/auth-client"; export const clientMiddleware = [authMiddleware]; @@ -27,16 +26,18 @@ export async function clientLoader({ context }: Route.LoaderArgs) { export default function Layout({ loaderData }: Route.ComponentProps) { const navigate = useNavigate(); - const logout = useMutation({ - ...logoutMutation(), - onSuccess: async () => { - void navigate("/login", { replace: true }); - }, - onError: (error) => { - console.error(error); - toast.error("Logout failed", { description: error.message }); - }, - }); + const handleLogout = async () => { + await authClient.signOut({ + fetchOptions: { + onSuccess: () => { + void navigate("/login", { replace: true }); + }, + onError: ({ error }) => { + toast.error("Logout failed", { description: error.message }); + }, + }, + }); + }; return ( @@ -54,7 +55,7 @@ export default function Layout({ loaderData }: Route.ComponentProps) { Welcome,  {loaderData.user?.username} - - + )} /> - diff --git a/app/client/modules/auth/routes/onboarding.tsx b/app/client/modules/auth/routes/onboarding.tsx index f95356f9..f9e3f490 100644 --- a/app/client/modules/auth/routes/onboarding.tsx +++ b/app/client/modules/auth/routes/onboarding.tsx @@ -1,5 +1,4 @@ import { arktypeResolver } from "@hookform/resolvers/arktype"; -import { useMutation } from "@tanstack/react-query"; import { type } from "arktype"; import { useForm } from "react-hook-form"; import { useNavigate } from "react-router"; @@ -18,7 +17,8 @@ import type { Route } from "./+types/onboarding"; import { AuthLayout } from "~/client/components/auth-layout"; import { Input } from "~/client/components/ui/input"; import { Button } from "~/client/components/ui/button"; -import { registerMutation } from "~/client/api-client/@tanstack/react-query.gen"; +import { authClient } from "~/client/lib/auth-client"; +import { useState } from "react"; export const clientMiddleware = [authMiddleware]; @@ -33,7 +33,8 @@ export function meta(_: Route.MetaArgs) { } const onboardingSchema = type({ - username: "2<=string<=50", + username: type("2<=string<=30").pipe((str) => str.trim().toLowerCase()), + email: type("string.email").pipe((str) => str.trim().toLowerCase()), password: "string>=8", confirmPassword: "string>=1", }); @@ -42,6 +43,7 @@ type OnboardingFormValues = typeof onboardingSchema.inferIn; export default function OnboardingPage() { const navigate = useNavigate(); + const [submitting, setSubmitting] = useState(false); const form = useForm({ resolver: arktypeResolver(onboardingSchema), @@ -49,22 +51,11 @@ export default function OnboardingPage() { username: "", password: "", confirmPassword: "", + email: "", }, }); - const registerUser = useMutation({ - ...registerMutation(), - onSuccess: async () => { - toast.success("Admin user created successfully!"); - void navigate("/download-recovery-key"); - }, - onError: (error) => { - console.error(error); - toast.error("Failed to create admin user", { description: error.message }); - }, - }); - - const onSubmit = (values: OnboardingFormValues) => { + const onSubmit = async (values: OnboardingFormValues) => { if (values.password !== values.confirmPassword) { form.setError("confirmPassword", { type: "manual", @@ -73,18 +64,50 @@ export default function OnboardingPage() { return; } - registerUser.mutate({ - body: { - username: values.username.trim(), - password: values.password.trim(), + const { data, error } = await authClient.signUp.email({ + username: values.username.toLowerCase().trim(), + password: values.password, + email: values.email.toLowerCase().trim(), + name: values.username, + displayUsername: values.username, + hasDownloadedResticPassword: false, + fetchOptions: { + onRequest: () => { + setSubmitting(true); + }, + onResponse: () => { + setSubmitting(false); + }, }, }); + + if (data?.token) { + toast.success("Admin user created successfully!"); + void navigate("/download-recovery-key"); + } else if (error) { + console.error(error); + toast.error("Failed to create admin user", { description: error.message }); + } }; return (
+ ( + + Email + + + + Enter your email address + + + )} + /> Username - + Choose a username for the admin account @@ -106,12 +129,7 @@ export default function OnboardingPage() { Password - + Password must be at least 8 characters long. @@ -125,19 +143,14 @@ export default function OnboardingPage() { Confirm Password - + )} /> - diff --git a/app/client/modules/settings/routes/settings.tsx b/app/client/modules/settings/routes/settings.tsx index 19b1f022..a1a75225 100644 --- a/app/client/modules/settings/routes/settings.tsx +++ b/app/client/modules/settings/routes/settings.tsx @@ -18,11 +18,8 @@ import { Input } from "~/client/components/ui/input"; import { Label } from "~/client/components/ui/label"; import { appContext } from "~/context"; import type { Route } from "./+types/settings"; -import { - changePasswordMutation, - downloadResticPasswordMutation, - logoutMutation, -} from "~/client/api-client/@tanstack/react-query.gen"; +import { downloadResticPasswordMutation } from "~/client/api-client/@tanstack/react-query.gen"; +import { authClient } from "~/client/lib/auth-client"; export const handle = { breadcrumb: () => [{ label: "Settings" }], @@ -49,31 +46,22 @@ export default function Settings({ loaderData }: Route.ComponentProps) { const [confirmPassword, setConfirmPassword] = useState(""); const [downloadDialogOpen, setDownloadDialogOpen] = useState(false); const [downloadPassword, setDownloadPassword] = useState(""); + const [isChangingPassword, setIsChangingPassword] = useState(false); const navigate = useNavigate(); - const logout = useMutation({ - ...logoutMutation(), - onSuccess: () => { - void navigate("/login", { replace: true }); - }, - }); - - const changePassword = useMutation({ - ...changePasswordMutation(), - onSuccess: (data) => { - if (data.success) { - toast.success("Password changed successfully. You will be logged out."); - setTimeout(() => { - logout.mutate({}); - }, 1500); - } else { - toast.error("Failed to change password", { description: data.message }); - } - }, - onError: (error) => { - toast.error("Failed to change password", { description: error.message }); - }, - }); + const handleLogout = async () => { + await authClient.signOut({ + fetchOptions: { + onSuccess: () => { + void navigate("/login", { replace: true }); + }, + onError: ({ error }) => { + console.error(error); + toast.error("Logout failed", { description: error.message }); + }, + }, + }); + }; const downloadResticPassword = useMutation({ ...downloadResticPasswordMutation(), @@ -97,7 +85,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) { }, }); - const handleChangePassword = (e: React.FormEvent) => { + const handleChangePassword = async (e: React.FormEvent) => { e.preventDefault(); if (newPassword !== confirmPassword) { @@ -110,10 +98,26 @@ export default function Settings({ loaderData }: Route.ComponentProps) { return; } - changePassword.mutate({ - body: { - currentPassword, - newPassword, + await authClient.changePassword({ + newPassword, + currentPassword: currentPassword, + revokeOtherSessions: true, + fetchOptions: { + onSuccess: () => { + toast.success("Password changed successfully. You will be logged out."); + setTimeout(() => { + void handleLogout(); + }, 1500); + }, + onError: (error) => { + toast.error("Failed to change password", { description: error.error.message }); + }, + onRequest: () => { + setIsChangingPassword(true); + }, + onResponse: () => { + setIsChangingPassword(false); + }, }, }); }; @@ -194,7 +198,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) { minLength={8} /> - diff --git a/app/context.ts b/app/context.ts index cd54625d..686511ad 100644 --- a/app/context.ts +++ b/app/context.ts @@ -1,5 +1,11 @@ import { createContext } from "react-router"; -import type { User } from "./client/lib/types"; + +type User = { + id: string; + email: string; + username: string; + hasDownloadedResticPassword: boolean; +}; type AppContext = { user: User | null; diff --git a/app/drizzle/0029_boring_luke_cage.sql b/app/drizzle/0029_boring_luke_cage.sql new file mode 100644 index 00000000..f7220de8 --- /dev/null +++ b/app/drizzle/0029_boring_luke_cage.sql @@ -0,0 +1,65 @@ +CREATE TABLE `account` ( + `id` text PRIMARY KEY NOT NULL, + `account_id` text NOT NULL, + `provider_id` text NOT NULL, + `user_id` text NOT NULL, + `access_token` text, + `refresh_token` text, + `id_token` text, + `access_token_expires_at` integer, + `refresh_token_expires_at` integer, + `scope` text, + `password` text, + `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `users_table`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE INDEX `account_userId_idx` ON `account` (`user_id`);--> statement-breakpoint +CREATE TABLE `verification` ( + `id` text PRIMARY KEY NOT NULL, + `identifier` text NOT NULL, + `value` text NOT NULL, + `expires_at` integer NOT NULL, + `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL +); +--> statement-breakpoint +CREATE INDEX `verification_identifier_idx` ON `verification` (`identifier`);--> statement-breakpoint +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_sessions_table` ( + `id` text PRIMARY KEY NOT NULL, + `user_id` text NOT NULL, + `token` text NOT NULL, + `expires_at` integer NOT NULL, + `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + `ip_address` text, + `user_agent` text, + FOREIGN KEY (`user_id`) REFERENCES `users_table`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +DROP TABLE `sessions_table`;--> statement-breakpoint +ALTER TABLE `__new_sessions_table` RENAME TO `sessions_table`;--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +CREATE UNIQUE INDEX `sessions_table_token_unique` ON `sessions_table` (`token`);--> statement-breakpoint +CREATE TABLE `__new_users_table` ( + `id` text PRIMARY KEY NOT NULL, + `username` text NOT NULL, + `password_hash` text, + `has_downloaded_restic_password` integer DEFAULT false NOT NULL, + `created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + `updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL, + `name` text NOT NULL, + `email` text NOT NULL, + `email_verified` integer DEFAULT false NOT NULL, + `image` text, + `display_username` text +); +--> statement-breakpoint +INSERT INTO `__new_users_table`("id", "username", "password_hash", "has_downloaded_restic_password", "created_at", "updated_at", "name", "email", "email_verified", "image", "display_username") SELECT "id", "username", "password_hash", "has_downloaded_restic_password", "created_at", "updated_at", "username", "username" || '@placeholder.local', false, "image", "username" FROM `users_table`;--> statement-breakpoint + +DROP TABLE `users_table`;--> statement-breakpoint +ALTER TABLE `__new_users_table` RENAME TO `users_table`;--> statement-breakpoint +CREATE UNIQUE INDEX `users_table_username_unique` ON `users_table` (`username`);--> statement-breakpoint +CREATE UNIQUE INDEX `users_table_email_unique` ON `users_table` (`email`); diff --git a/app/drizzle/0030_lower-trim-username.sql b/app/drizzle/0030_lower-trim-username.sql new file mode 100644 index 00000000..bf39e426 --- /dev/null +++ b/app/drizzle/0030_lower-trim-username.sql @@ -0,0 +1,2 @@ +-- Custom SQL migration file, put your code below! -- +UPDATE users_table SET username = LOWER(TRIM(username)); diff --git a/app/drizzle/meta/0029_snapshot.json b/app/drizzle/meta/0029_snapshot.json new file mode 100644 index 00000000..47a5496e --- /dev/null +++ b/app/drizzle/meta/0029_snapshot.json @@ -0,0 +1,1113 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "2962ba85-b9d5-457b-b4cb-46b49ba13006", + "prevId": "2837bed4-34fb-4d16-b331-7b6d483979bc", + "tables": { + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "account_user_id_users_table_id_fk": { + "name": "account_user_id_users_table_id_fk", + "tableFrom": "account", + "tableTo": "users_table", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "app_metadata": { + "name": "app_metadata", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "backup_schedule_mirrors_table": { + "name": "backup_schedule_mirrors_table", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "last_copy_at": { + "name": "last_copy_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_copy_status": { + "name": "last_copy_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_copy_error": { + "name": "last_copy_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "backup_schedule_mirrors_table_schedule_id_repository_id_unique": { + "name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique", + "columns": [ + "schedule_id", + "repository_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk": { + "name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk", + "tableFrom": "backup_schedule_mirrors_table", + "tableTo": "backup_schedules_table", + "columnsFrom": [ + "schedule_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk": { + "name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk", + "tableFrom": "backup_schedule_mirrors_table", + "tableTo": "repositories_table", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "backup_schedule_notifications_table": { + "name": "backup_schedule_notifications_table", + "columns": { + "schedule_id": { + "name": "schedule_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "destination_id": { + "name": "destination_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "notify_on_start": { + "name": "notify_on_start", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notify_on_success": { + "name": "notify_on_success", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notify_on_warning": { + "name": "notify_on_warning", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "notify_on_failure": { + "name": "notify_on_failure", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": {}, + "foreignKeys": { + "backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk": { + "name": "backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk", + "tableFrom": "backup_schedule_notifications_table", + "tableTo": "backup_schedules_table", + "columnsFrom": [ + "schedule_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk": { + "name": "backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk", + "tableFrom": "backup_schedule_notifications_table", + "tableTo": "notification_destinations_table", + "columnsFrom": [ + "destination_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "backup_schedule_notifications_table_schedule_id_destination_id_pk": { + "columns": [ + "schedule_id", + "destination_id" + ], + "name": "backup_schedule_notifications_table_schedule_id_destination_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "backup_schedules_table": { + "name": "backup_schedules_table", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "short_id": { + "name": "short_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "volume_id": { + "name": "volume_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retention_policy": { + "name": "retention_policy", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "exclude_patterns": { + "name": "exclude_patterns", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'[]'" + }, + "exclude_if_present": { + "name": "exclude_if_present", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'[]'" + }, + "include_patterns": { + "name": "include_patterns", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'[]'" + }, + "last_backup_at": { + "name": "last_backup_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_backup_status": { + "name": "last_backup_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_backup_error": { + "name": "last_backup_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "next_backup_at": { + "name": "next_backup_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "one_file_system": { + "name": "one_file_system", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "backup_schedules_table_short_id_unique": { + "name": "backup_schedules_table_short_id_unique", + "columns": [ + "short_id" + ], + "isUnique": true + }, + "backup_schedules_table_name_unique": { + "name": "backup_schedules_table_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": { + "backup_schedules_table_volume_id_volumes_table_id_fk": { + "name": "backup_schedules_table_volume_id_volumes_table_id_fk", + "tableFrom": "backup_schedules_table", + "tableTo": "volumes_table", + "columnsFrom": [ + "volume_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_schedules_table_repository_id_repositories_table_id_fk": { + "name": "backup_schedules_table_repository_id_repositories_table_id_fk", + "tableFrom": "backup_schedules_table", + "tableTo": "repositories_table", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_destinations_table": { + "name": "notification_destinations_table", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "notification_destinations_table_name_unique": { + "name": "notification_destinations_table_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repositories_table": { + "name": "repositories_table", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "short_id": { + "name": "short_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "compression_mode": { + "name": "compression_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'auto'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'unknown'" + }, + "last_checked": { + "name": "last_checked", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "repositories_table_short_id_unique": { + "name": "repositories_table_short_id_unique", + "columns": [ + "short_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions_table": { + "name": "sessions_table", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "sessions_table_token_unique": { + "name": "sessions_table_token_unique", + "columns": [ + "token" + ], + "isUnique": true + } + }, + "foreignKeys": { + "sessions_table_user_id_users_table_id_fk": { + "name": "sessions_table_user_id_users_table_id_fk", + "tableFrom": "sessions_table", + "tableTo": "users_table", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users_table": { + "name": "users_table", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "has_downloaded_restic_password": { + "name": "has_downloaded_restic_password", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_username": { + "name": "display_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "users_table_username_unique": { + "name": "users_table_username_unique", + "columns": [ + "username" + ], + "isUnique": true + }, + "users_table_email_unique": { + "name": "users_table_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "volumes_table": { + "name": "volumes_table", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "short_id": { + "name": "short_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unmounted'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_health_check": { + "name": "last_health_check", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "auto_remount": { + "name": "auto_remount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "volumes_table_short_id_unique": { + "name": "volumes_table_short_id_unique", + "columns": [ + "short_id" + ], + "isUnique": true + }, + "volumes_table_name_unique": { + "name": "volumes_table_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/app/drizzle/meta/0030_snapshot.json b/app/drizzle/meta/0030_snapshot.json new file mode 100644 index 00000000..c31c3e12 --- /dev/null +++ b/app/drizzle/meta/0030_snapshot.json @@ -0,0 +1,1113 @@ +{ + "id": "37737391-3263-4d25-b4d3-417d7f981eb1", + "prevId": "2962ba85-b9d5-457b-b4cb-46b49ba13006", + "version": "6", + "dialect": "sqlite", + "tables": { + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "account_user_id_users_table_id_fk": { + "name": "account_user_id_users_table_id_fk", + "tableFrom": "account", + "columnsFrom": [ + "user_id" + ], + "tableTo": "users_table", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "app_metadata": { + "name": "app_metadata", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "backup_schedule_mirrors_table": { + "name": "backup_schedule_mirrors_table", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "last_copy_at": { + "name": "last_copy_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_copy_status": { + "name": "last_copy_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_copy_error": { + "name": "last_copy_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "backup_schedule_mirrors_table_schedule_id_repository_id_unique": { + "name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique", + "columns": [ + "schedule_id", + "repository_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk": { + "name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk", + "tableFrom": "backup_schedule_mirrors_table", + "columnsFrom": [ + "schedule_id" + ], + "tableTo": "backup_schedules_table", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk": { + "name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk", + "tableFrom": "backup_schedule_mirrors_table", + "columnsFrom": [ + "repository_id" + ], + "tableTo": "repositories_table", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "backup_schedule_notifications_table": { + "name": "backup_schedule_notifications_table", + "columns": { + "schedule_id": { + "name": "schedule_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "destination_id": { + "name": "destination_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "notify_on_start": { + "name": "notify_on_start", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notify_on_success": { + "name": "notify_on_success", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notify_on_warning": { + "name": "notify_on_warning", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "notify_on_failure": { + "name": "notify_on_failure", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": {}, + "foreignKeys": { + "backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk": { + "name": "backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk", + "tableFrom": "backup_schedule_notifications_table", + "columnsFrom": [ + "schedule_id" + ], + "tableTo": "backup_schedules_table", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk": { + "name": "backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk", + "tableFrom": "backup_schedule_notifications_table", + "columnsFrom": [ + "destination_id" + ], + "tableTo": "notification_destinations_table", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "backup_schedule_notifications_table_schedule_id_destination_id_pk": { + "columns": [ + "schedule_id", + "destination_id" + ], + "name": "backup_schedule_notifications_table_schedule_id_destination_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "backup_schedules_table": { + "name": "backup_schedules_table", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "short_id": { + "name": "short_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "volume_id": { + "name": "volume_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retention_policy": { + "name": "retention_policy", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "exclude_patterns": { + "name": "exclude_patterns", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'[]'" + }, + "exclude_if_present": { + "name": "exclude_if_present", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'[]'" + }, + "include_patterns": { + "name": "include_patterns", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'[]'" + }, + "last_backup_at": { + "name": "last_backup_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_backup_status": { + "name": "last_backup_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_backup_error": { + "name": "last_backup_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "next_backup_at": { + "name": "next_backup_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "one_file_system": { + "name": "one_file_system", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "backup_schedules_table_short_id_unique": { + "name": "backup_schedules_table_short_id_unique", + "columns": [ + "short_id" + ], + "isUnique": true + }, + "backup_schedules_table_name_unique": { + "name": "backup_schedules_table_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": { + "backup_schedules_table_volume_id_volumes_table_id_fk": { + "name": "backup_schedules_table_volume_id_volumes_table_id_fk", + "tableFrom": "backup_schedules_table", + "columnsFrom": [ + "volume_id" + ], + "tableTo": "volumes_table", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "backup_schedules_table_repository_id_repositories_table_id_fk": { + "name": "backup_schedules_table_repository_id_repositories_table_id_fk", + "tableFrom": "backup_schedules_table", + "columnsFrom": [ + "repository_id" + ], + "tableTo": "repositories_table", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_destinations_table": { + "name": "notification_destinations_table", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "notification_destinations_table_name_unique": { + "name": "notification_destinations_table_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repositories_table": { + "name": "repositories_table", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "short_id": { + "name": "short_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "compression_mode": { + "name": "compression_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'auto'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'unknown'" + }, + "last_checked": { + "name": "last_checked", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "repositories_table_short_id_unique": { + "name": "repositories_table_short_id_unique", + "columns": [ + "short_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions_table": { + "name": "sessions_table", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "sessions_table_token_unique": { + "name": "sessions_table_token_unique", + "columns": [ + "token" + ], + "isUnique": true + } + }, + "foreignKeys": { + "sessions_table_user_id_users_table_id_fk": { + "name": "sessions_table_user_id_users_table_id_fk", + "tableFrom": "sessions_table", + "columnsFrom": [ + "user_id" + ], + "tableTo": "users_table", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users_table": { + "name": "users_table", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "has_downloaded_restic_password": { + "name": "has_downloaded_restic_password", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_username": { + "name": "display_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "users_table_username_unique": { + "name": "users_table_username_unique", + "columns": [ + "username" + ], + "isUnique": true + }, + "users_table_email_unique": { + "name": "users_table_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "volumes_table": { + "name": "volumes_table", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "short_id": { + "name": "short_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unmounted'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_health_check": { + "name": "last_health_check", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "auto_remount": { + "name": "auto_remount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "volumes_table_short_id_unique": { + "name": "volumes_table_short_id_unique", + "columns": [ + "short_id" + ], + "isUnique": true + }, + "volumes_table_name_unique": { + "name": "volumes_table_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/app/drizzle/meta/_journal.json b/app/drizzle/meta/_journal.json index afa41ba8..ac6eb8f9 100644 --- a/app/drizzle/meta/_journal.json +++ b/app/drizzle/meta/_journal.json @@ -1,209 +1,223 @@ { - "version": "7", - "dialect": "sqlite", - "entries": [ - { - "idx": 0, - "version": "6", - "when": 1755765658194, - "tag": "0000_known_madelyne_pryor", - "breakpoints": true - }, - { - "idx": 1, - "version": "6", - "when": 1755775437391, - "tag": "0001_far_frank_castle", - "breakpoints": true - }, - { - "idx": 2, - "version": "6", - "when": 1756930554198, - "tag": "0002_cheerful_randall", - "breakpoints": true - }, - { - "idx": 3, - "version": "6", - "when": 1758653407064, - "tag": "0003_mature_hellcat", - "breakpoints": true - }, - { - "idx": 4, - "version": "6", - "when": 1758961535488, - "tag": "0004_wealthy_tomas", - "breakpoints": true - }, - { - "idx": 5, - "version": "6", - "when": 1759416698274, - "tag": "0005_simple_alice", - "breakpoints": true - }, - { - "idx": 6, - "version": "6", - "when": 1760734377440, - "tag": "0006_secret_micromacro", - "breakpoints": true - }, - { - "idx": 7, - "version": "6", - "when": 1761224911352, - "tag": "0007_watery_sersi", - "breakpoints": true - }, - { - "idx": 8, - "version": "6", - "when": 1761414054481, - "tag": "0008_silent_lady_bullseye", - "breakpoints": true - }, - { - "idx": 9, - "version": "6", - "when": 1762095226041, - "tag": "0009_little_adam_warlock", - "breakpoints": true - }, - { - "idx": 10, - "version": "6", - "when": 1762610065889, - "tag": "0010_perfect_proemial_gods", - "breakpoints": true - }, - { - "idx": 11, - "version": "6", - "when": 1763644043601, - "tag": "0011_familiar_stone_men", - "breakpoints": true - }, - { - "idx": 12, - "version": "6", - "when": 1764100562084, - "tag": "0012_add_short_ids", - "breakpoints": true - }, - { - "idx": 13, - "version": "6", - "when": 1764182159797, - "tag": "0013_elite_sprite", - "breakpoints": true - }, - { - "idx": 14, - "version": "6", - "when": 1764182405089, - "tag": "0014_wild_echo", - "breakpoints": true - }, - { - "idx": 15, - "version": "6", - "when": 1764182465287, - "tag": "0015_jazzy_sersi", - "breakpoints": true - }, - { - "idx": 16, - "version": "6", - "when": 1764194697035, - "tag": "0016_fix-timestamps-to-ms", - "breakpoints": true - }, - { - "idx": 17, - "version": "6", - "when": 1764357897219, - "tag": "0017_fix-compression-modes", - "breakpoints": true - }, - { - "idx": 18, - "version": "6", - "when": 1764794371040, - "tag": "0018_breezy_invaders", - "breakpoints": true - }, - { - "idx": 19, - "version": "6", - "when": 1764839917446, - "tag": "0019_secret_nomad", - "breakpoints": true - }, - { - "idx": 20, - "version": "6", - "when": 1764847918249, - "tag": "0020_even_dexter_bennett", - "breakpoints": true - }, - { - "idx": 21, - "version": "6", - "when": 1765307881092, - "tag": "0021_steady_viper", - "breakpoints": true - }, - { - "idx": 22, - "version": "6", - "when": 1765794552191, - "tag": "0022_woozy_shen", - "breakpoints": true - }, - { - "idx": 23, - "version": "6", - "when": 1766320570509, - "tag": "0023_special_thor", - "breakpoints": true - }, - { - "idx": 24, - "version": "6", - "when": 1766325504548, - "tag": "0024_schedules-one-fs", - "breakpoints": true - }, - { - "idx": 25, - "version": "6", - "when": 1766431021321, - "tag": "0025_remarkable_pete_wisdom", - "breakpoints": true - }, - { - "idx": 26, - "version": "6", - "when": 1766765013108, - "tag": "0026_migrate-local-repo-paths", - "breakpoints": true - }, - { - "idx": 27, - "version": "6", - "when": 1766778073418, - "tag": "0027_careful_cammi", - "breakpoints": true - }, - { - "idx": 28, - "version": "6", - "when": 1766778162985, - "tag": "0028_third_amazoness", - "breakpoints": true - } - ] -} + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1755765658194, + "tag": "0000_known_madelyne_pryor", + "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1755775437391, + "tag": "0001_far_frank_castle", + "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1756930554198, + "tag": "0002_cheerful_randall", + "breakpoints": true + }, + { + "idx": 3, + "version": "6", + "when": 1758653407064, + "tag": "0003_mature_hellcat", + "breakpoints": true + }, + { + "idx": 4, + "version": "6", + "when": 1758961535488, + "tag": "0004_wealthy_tomas", + "breakpoints": true + }, + { + "idx": 5, + "version": "6", + "when": 1759416698274, + "tag": "0005_simple_alice", + "breakpoints": true + }, + { + "idx": 6, + "version": "6", + "when": 1760734377440, + "tag": "0006_secret_micromacro", + "breakpoints": true + }, + { + "idx": 7, + "version": "6", + "when": 1761224911352, + "tag": "0007_watery_sersi", + "breakpoints": true + }, + { + "idx": 8, + "version": "6", + "when": 1761414054481, + "tag": "0008_silent_lady_bullseye", + "breakpoints": true + }, + { + "idx": 9, + "version": "6", + "when": 1762095226041, + "tag": "0009_little_adam_warlock", + "breakpoints": true + }, + { + "idx": 10, + "version": "6", + "when": 1762610065889, + "tag": "0010_perfect_proemial_gods", + "breakpoints": true + }, + { + "idx": 11, + "version": "6", + "when": 1763644043601, + "tag": "0011_familiar_stone_men", + "breakpoints": true + }, + { + "idx": 12, + "version": "6", + "when": 1764100562084, + "tag": "0012_add_short_ids", + "breakpoints": true + }, + { + "idx": 13, + "version": "6", + "when": 1764182159797, + "tag": "0013_elite_sprite", + "breakpoints": true + }, + { + "idx": 14, + "version": "6", + "when": 1764182405089, + "tag": "0014_wild_echo", + "breakpoints": true + }, + { + "idx": 15, + "version": "6", + "when": 1764182465287, + "tag": "0015_jazzy_sersi", + "breakpoints": true + }, + { + "idx": 16, + "version": "6", + "when": 1764194697035, + "tag": "0016_fix-timestamps-to-ms", + "breakpoints": true + }, + { + "idx": 17, + "version": "6", + "when": 1764357897219, + "tag": "0017_fix-compression-modes", + "breakpoints": true + }, + { + "idx": 18, + "version": "6", + "when": 1764794371040, + "tag": "0018_breezy_invaders", + "breakpoints": true + }, + { + "idx": 19, + "version": "6", + "when": 1764839917446, + "tag": "0019_secret_nomad", + "breakpoints": true + }, + { + "idx": 20, + "version": "6", + "when": 1764847918249, + "tag": "0020_even_dexter_bennett", + "breakpoints": true + }, + { + "idx": 21, + "version": "6", + "when": 1765307881092, + "tag": "0021_steady_viper", + "breakpoints": true + }, + { + "idx": 22, + "version": "6", + "when": 1765794552191, + "tag": "0022_woozy_shen", + "breakpoints": true + }, + { + "idx": 23, + "version": "6", + "when": 1766320570509, + "tag": "0023_special_thor", + "breakpoints": true + }, + { + "idx": 24, + "version": "6", + "when": 1766325504548, + "tag": "0024_schedules-one-fs", + "breakpoints": true + }, + { + "idx": 25, + "version": "6", + "when": 1766431021321, + "tag": "0025_remarkable_pete_wisdom", + "breakpoints": true + }, + { + "idx": 26, + "version": "6", + "when": 1766765013108, + "tag": "0026_migrate-local-repo-paths", + "breakpoints": true + }, + { + "idx": 27, + "version": "6", + "when": 1766778073418, + "tag": "0027_careful_cammi", + "breakpoints": true + }, + { + "idx": 28, + "version": "6", + "when": 1766778162985, + "tag": "0028_third_amazoness", + "breakpoints": true + }, + { + "idx": 29, + "version": "6", + "when": 1767819883495, + "tag": "0029_boring_luke_cage", + "breakpoints": true + }, + { + "idx": 30, + "version": "6", + "when": 1767821088612, + "tag": "0030_lower-trim-username", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/app/lib/auth-middlewares/convert-legacy-user.ts b/app/lib/auth-middlewares/convert-legacy-user.ts new file mode 100644 index 00000000..f4ce3b35 --- /dev/null +++ b/app/lib/auth-middlewares/convert-legacy-user.ts @@ -0,0 +1,50 @@ +import { hashPassword } from "better-auth/crypto"; +import { and, eq, ne } from "drizzle-orm"; +import { db } from "~/server/db/db"; +import { account, usersTable } from "~/server/db/schema"; +import type { AuthMiddlewareContext } from "../auth"; + +export const convertLegacyUserOnFirstLogin = async (ctx: AuthMiddlewareContext) => { + const { path, body } = ctx; + + if (path !== "/sign-in/username") { + return; + } + + const legacyUser = await db.query.usersTable.findFirst({ + where: and(eq(usersTable.username, body.username.trim().toLowerCase()), ne(usersTable.passwordHash, "")), + }); + + if (legacyUser) { + const isValid = await Bun.password.verify(body.password, legacyUser.passwordHash ?? ""); + + if (isValid) { + await db.transaction(async (tx) => { + const newUserId = crypto.randomUUID(); + const accountId = crypto.randomUUID(); + + await tx.delete(usersTable).where(eq(usersTable.id, legacyUser.id)); + + await tx.insert(usersTable).values({ + id: newUserId, + username: legacyUser.username, + email: legacyUser.email, + name: legacyUser.name, + hasDownloadedResticPassword: legacyUser.hasDownloadedResticPassword, + emailVerified: false, + }); + + await tx.insert(account).values({ + id: accountId, + providerId: "credential", + accountId: legacyUser.username, + userId: newUserId, + password: await hashPassword(body.password), + createdAt: new Date(), + }); + }); + } else { + throw new Error("Invalid credentials"); + } + } +}; diff --git a/app/lib/auth-middlewares/only-one-user.ts b/app/lib/auth-middlewares/only-one-user.ts new file mode 100644 index 00000000..92c09a06 --- /dev/null +++ b/app/lib/auth-middlewares/only-one-user.ts @@ -0,0 +1,17 @@ +import { db } from "~/server/db/db"; +import type { AuthMiddlewareContext } from "../auth"; +import { logger } from "~/server/utils/logger"; + +export const ensureOnlyOneUser = async (ctx: AuthMiddlewareContext) => { + const { path } = ctx; + + if (path !== "/sign-up/email") { + return; + } + + const existingUser = await db.query.usersTable.findFirst(); + if (existingUser) { + logger.error("Attempt to create a second administrator account blocked."); + throw new Error("An administrator account already exists"); + } +}; diff --git a/app/lib/auth.ts b/app/lib/auth.ts new file mode 100644 index 00000000..049295a2 --- /dev/null +++ b/app/lib/auth.ts @@ -0,0 +1,49 @@ +import { + betterAuth, + type AuthContext, + type BetterAuthOptions, + type MiddlewareContext, + type MiddlewareOptions, +} from "better-auth"; +import { drizzleAdapter } from "better-auth/adapters/drizzle"; +import { createAuthMiddleware, username } from "better-auth/plugins"; +import { convertLegacyUserOnFirstLogin } from "./auth-middlewares/convert-legacy-user"; +import { cryptoUtils } from "~/server/utils/crypto"; +import { db } from "~/server/db/db"; +import { ensureOnlyOneUser } from "./auth-middlewares/only-one-user"; + +export type AuthMiddlewareContext = MiddlewareContext>; + +export const auth = betterAuth({ + secret: await cryptoUtils.deriveSecret("better-auth"), + hooks: { + before: createAuthMiddleware(async (ctx) => { + await ensureOnlyOneUser(ctx); + await convertLegacyUserOnFirstLogin(ctx); + }), + }, + database: drizzleAdapter(db, { + provider: "sqlite", + }), + emailAndPassword: { + enabled: true, + }, + user: { + modelName: "usersTable", + additionalFields: { + username: { + type: "string", + returned: true, + required: true, + }, + hasDownloadedResticPassword: { + type: "boolean", + returned: true, + }, + }, + }, + session: { + modelName: "sessionsTable", + }, + plugins: [username({})], +}); diff --git a/app/middleware/auth.ts b/app/middleware/auth.ts index e389df40..aa4545b3 100644 --- a/app/middleware/auth.ts +++ b/app/middleware/auth.ts @@ -1,13 +1,14 @@ import { redirect, type MiddlewareFunction } from "react-router"; -import { getMe, getStatus } from "~/client/api-client"; +import { getStatus } from "~/client/api-client"; +import { authClient } from "~/client/lib/auth-client"; import { appContext } from "~/context"; export const authMiddleware: MiddlewareFunction = async ({ context, request }) => { - const session = await getMe(); + const { data: session } = await authClient.getSession(); const isAuthRoute = ["/login", "/onboarding"].includes(new URL(request.url).pathname); - if (!session.data?.user?.id && !isAuthRoute) { + if (!session?.user?.id && !isAuthRoute) { const status = await getStatus(); if (!status.data?.hasUsers) { throw redirect("/onboarding"); @@ -16,8 +17,8 @@ export const authMiddleware: MiddlewareFunction = async ({ context, request }) = throw redirect("/login"); } - if (session.data?.user?.id) { - context.set(appContext, { user: session.data.user, hasUsers: true }); + if (session?.user?.id) { + context.set(appContext, { user: session.user, hasUsers: true }); if (isAuthRoute) { throw redirect("/"); diff --git a/app/server/app.ts b/app/server/app.ts index 93ed52ae..e5bae37e 100644 --- a/app/server/app.ts +++ b/app/server/app.ts @@ -15,6 +15,7 @@ import { notificationsController } from "./modules/notifications/notifications.c import { handleServiceError } from "./utils/errors"; import { logger } from "./utils/logger"; import { config } from "./core/config"; +import { auth } from "~/lib/auth"; export const generalDescriptor = (app: Hono) => openAPIRouteHandler(app, { @@ -62,6 +63,7 @@ export const createApp = () => { .route("/api/v1/system", systemController) .route("/api/v1/events", eventsController); + app.on(["POST", "GET"], "/api/auth/*", (c) => auth.handler(c.req.raw)); app.get("/api/v1/openapi.json", generalDescriptor(app)); app.get("/api/v1/docs", requireAuth, scalarDescriptor); diff --git a/app/server/cli/commands/reset-password.ts b/app/server/cli/commands/reset-password.ts index 93d200ad..ae482c6d 100644 --- a/app/server/cli/commands/reset-password.ts +++ b/app/server/cli/commands/reset-password.ts @@ -1,28 +1,45 @@ -import { Command } from "commander"; import { password, select } from "@inquirer/prompts"; -import { eq } from "drizzle-orm"; +import { hashPassword } from "better-auth/crypto"; +import { Command } from "commander"; +import { and, eq } from "drizzle-orm"; +import { toMessage } from "~/server/utils/errors"; import { db } from "../../db/db"; -import { sessionsTable, usersTable } from "../../db/schema"; +import { account, sessionsTable, usersTable } from "../../db/schema"; const listUsers = () => { - return db.select({ id: usersTable.id, username: usersTable.username }).from(usersTable); + return db + .select({ id: usersTable.id, username: usersTable.username }) + .from(usersTable); }; const resetPassword = async (username: string, newPassword: string) => { - const [user] = await db.select().from(usersTable).where(eq(usersTable.username, username)); + const [user] = await db + .select() + .from(usersTable) + .where(eq(usersTable.username, username)); if (!user) { throw new Error(`User "${username}" not found`); } - const newPasswordHash = await Bun.password.hash(newPassword, { - algorithm: "argon2id", - memoryCost: 19456, - timeCost: 2, - }); + const newPasswordHash = await hashPassword(newPassword); await db.transaction(async (tx) => { - await tx.update(usersTable).set({ passwordHash: newPasswordHash }).where(eq(usersTable.id, user.id)); + await tx + .update(account) + .set({ password: newPasswordHash }) + .where( + and(eq(account.userId, user.id), eq(account.providerId, "credential")), + ); + + if (user.passwordHash) { + const legacyHash = await Bun.password.hash(newPassword); + await tx + .update(usersTable) + .set({ passwordHash: legacyHash }) + .where(eq(usersTable.id, user.id)); + } + await tx.delete(sessionsTable).where(eq(sessionsTable.userId, user.id)); }); }; @@ -42,7 +59,9 @@ export const resetPasswordCommand = new Command("reset-password") if (users.length === 0) { console.error("❌ No users found in the database."); - console.log(" Please create a user first by starting the application."); + console.log( + " Please create a user first by starting the application.", + ); process.exit(1); } @@ -80,10 +99,12 @@ export const resetPasswordCommand = new Command("reset-password") try { await resetPassword(username, newPassword); - console.log(`\n✅ Password for user "${username}" has been reset successfully.`); + console.log( + `\n✅ Password for user "${username}" has been reset successfully.`, + ); console.log(" All existing sessions have been invalidated."); } catch (error) { - console.error(`\n❌ Failed to reset password: ${error instanceof Error ? error.message : "Unknown error"}`); + console.error(`\n❌ Failed to reset password: ${toMessage(error)}`); process.exit(1); } diff --git a/app/server/db/db.ts b/app/server/db/db.ts index 9a711032..7e992a9b 100644 --- a/app/server/db/db.ts +++ b/app/server/db/db.ts @@ -1,17 +1,51 @@ -import "dotenv/config"; import { Database } from "bun:sqlite"; import path from "node:path"; import { drizzle } from "drizzle-orm/bun-sqlite"; import { migrate } from "drizzle-orm/bun-sqlite/migrator"; import { DATABASE_URL } from "../core/constants"; -import * as schema from "./schema"; -import fs from "node:fs/promises"; +import fs from "node:fs"; import { config } from "../core/config"; +import type * as schemaTypes from "./schema"; -await fs.mkdir(path.dirname(DATABASE_URL), { recursive: true }); +/** + * TODO: try to remove this if moving away from react-router. + * The rr vite plugin doesn't let us customize the chunk names + * to isolate the db initialization code from the rest of the server code. + */ +let _sqlite: Database | undefined; +let _db: ReturnType> | undefined; +let _schema: typeof schemaTypes | undefined; -const sqlite = new Database(DATABASE_URL); -export const db = drizzle({ client: sqlite, schema }); +/** + * Sets the database schema. This must be called before any database operations. + */ +export const setSchema = (schema: typeof schemaTypes) => { + _schema = schema; +}; + +const initDb = () => { + if (!_schema) { + throw new Error("Database schema not set. Call setSchema() before accessing the database."); + } + fs.mkdirSync(path.dirname(DATABASE_URL), { recursive: true }); + _sqlite = new Database(DATABASE_URL); + return drizzle({ client: _sqlite, schema: _schema }); +}; + +/** + * Database instance (Proxy for lazy initialization) + */ +export const db = new Proxy( + {}, + { + get(_, prop, receiver) { + if (!_db) { + _db = initDb(); + } + return Reflect.get(_db, prop, receiver); + }, + }, +) as ReturnType>; export const runDbMigrations = () => { let migrationsFolder: string; @@ -26,5 +60,9 @@ export const runDbMigrations = () => { migrate(db, { migrationsFolder }); - sqlite.run("PRAGMA foreign_keys = ON;"); + if (!_sqlite) { + throw new Error("Database not initialized"); + } + + _sqlite.run("PRAGMA foreign_keys = ON;"); }; diff --git a/app/server/db/schema.ts b/app/server/db/schema.ts index 61c82982..aeeaccf1 100644 --- a/app/server/db/schema.ts +++ b/app/server/db/schema.ts @@ -1,5 +1,5 @@ import { relations, sql } from "drizzle-orm"; -import { int, integer, sqliteTable, text, primaryKey, unique } from "drizzle-orm/sqlite-core"; +import { index, int, integer, sqliteTable, text, primaryKey, unique } from "drizzle-orm/sqlite-core"; import type { CompressionMode, RepositoryBackend, repositoryConfigSchema, RepositoryStatus } from "~/schemas/restic"; import type { BackendStatus, BackendType, volumeConfigSchema } from "~/schemas/volumes"; import type { NotificationType, notificationConfigSchema } from "~/schemas/notifications"; @@ -14,9 +14,16 @@ export const volumesTable = sqliteTable("volumes_table", { type: text().$type().notNull(), status: text().$type().notNull().default("unmounted"), lastError: text("last_error"), - lastHealthCheck: integer("last_health_check", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`), - createdAt: integer("created_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`), - updatedAt: integer("updated_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`), + lastHealthCheck: integer("last_health_check", { mode: "number" }) + .notNull() + .default(sql`(unixepoch() * 1000)`), + createdAt: integer("created_at", { mode: "number" }) + .notNull() + .default(sql`(unixepoch() * 1000)`), + updatedAt: integer("updated_at", { mode: "number" }) + .notNull() + .$onUpdate(() => Date.now()) + .default(sql`(unixepoch() * 1000)`), config: text("config", { mode: "json" }).$type().notNull(), autoRemount: int("auto_remount", { mode: "boolean" }).notNull().default(true), }); @@ -27,24 +34,112 @@ export type VolumeInsert = typeof volumesTable.$inferInsert; * Users Table */ export const usersTable = sqliteTable("users_table", { - id: int().primaryKey({ autoIncrement: true }), + id: text("id").primaryKey(), username: text().notNull().unique(), - passwordHash: text("password_hash").notNull(), + passwordHash: text("password_hash"), hasDownloadedResticPassword: int("has_downloaded_restic_password", { mode: "boolean" }).notNull().default(false), - createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`), - updatedAt: int("updated_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`), + createdAt: int("created_at", { mode: "timestamp_ms" }) + .notNull() + .default(sql`(unixepoch() * 1000)`), + updatedAt: int("updated_at", { mode: "timestamp_ms" }) + .notNull() + .$onUpdate(() => new Date()) + .default(sql`(unixepoch() * 1000)`), + name: text("name").notNull(), + email: text("email").notNull().unique(), + emailVerified: integer("email_verified", { mode: "boolean" }).default(false).notNull(), + image: text("image"), + displayUsername: text("display_username"), }); + export type User = typeof usersTable.$inferSelect; export const sessionsTable = sqliteTable("sessions_table", { id: text().primaryKey(), - userId: int("user_id") + userId: text("user_id") .notNull() .references(() => usersTable.id, { onDelete: "cascade" }), - expiresAt: int("expires_at", { mode: "number" }).notNull(), - createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`), + token: text("token").notNull().unique(), + expiresAt: int("expires_at", { mode: "timestamp_ms" }).notNull(), + createdAt: int("created_at", { mode: "timestamp_ms" }) + .notNull() + .default(sql`(unixepoch() * 1000)`), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }) + .notNull() + .$onUpdate(() => new Date()) + .default(sql`(unixepoch() * 1000)`), + ipAddress: text("ip_address"), + userAgent: text("user_agent"), }); export type Session = typeof sessionsTable.$inferSelect; +export const account = sqliteTable( + "account", + { + id: text("id").primaryKey(), + accountId: text("account_id").notNull(), + providerId: text("provider_id").notNull(), + userId: text("user_id") + .notNull() + .references(() => usersTable.id, { onDelete: "cascade" }), + accessToken: text("access_token"), + refreshToken: text("refresh_token"), + idToken: text("id_token"), + accessTokenExpiresAt: integer("access_token_expires_at", { + mode: "timestamp_ms", + }), + refreshTokenExpiresAt: integer("refresh_token_expires_at", { + mode: "timestamp_ms", + }), + scope: text("scope"), + password: text("password"), + createdAt: integer("created_at", { mode: "timestamp_ms" }) + .notNull() + .default(sql`(unixepoch() * 1000)`), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }) + .$onUpdate(() => new Date()) + .notNull() + .default(sql`(unixepoch() * 1000)`), + }, + (table) => [index("account_userId_idx").on(table.userId)], +); + +export const verification = sqliteTable( + "verification", + { + id: text("id").primaryKey(), + identifier: text("identifier").notNull(), + value: text("value").notNull(), + expiresAt: integer("expires_at", { mode: "number" }).notNull(), + createdAt: integer("created_at", { mode: "number" }) + .notNull() + .default(sql`(unixepoch() * 1000)`), + updatedAt: integer("updated_at", { mode: "number" }) + .$onUpdate(() => Date.now()) + .notNull() + .default(sql`(unixepoch() * 1000)`), + }, + (table) => [index("verification_identifier_idx").on(table.identifier)], +); + +export const userRelations = relations(usersTable, ({ many }) => ({ + sessions: many(sessionsTable), + accounts: many(account), +})); + +export const sessionRelations = relations(sessionsTable, ({ one }) => ({ + user: one(usersTable, { + fields: [sessionsTable.userId], + references: [usersTable.id], + }), +})); + +export const accountRelations = relations(account, ({ one }) => ({ + user: one(usersTable, { + fields: [account.userId], + references: [usersTable.id], + }), +})); + /** * Repositories Table */ @@ -58,8 +153,12 @@ export const repositoriesTable = sqliteTable("repositories_table", { status: text().$type().default("unknown"), lastChecked: int("last_checked", { mode: "number" }), lastError: text("last_error"), - createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`), - updatedAt: int("updated_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`), + createdAt: int("created_at", { mode: "number" }) + .notNull() + .default(sql`(unixepoch() * 1000)`), + updatedAt: int("updated_at", { mode: "number" }) + .notNull() + .default(sql`(unixepoch() * 1000)`), }); export type Repository = typeof repositoriesTable.$inferSelect; export type RepositoryInsert = typeof repositoriesTable.$inferInsert; @@ -97,8 +196,12 @@ export const backupSchedulesTable = sqliteTable("backup_schedules_table", { nextBackupAt: int("next_backup_at", { mode: "number" }), oneFileSystem: int("one_file_system", { mode: "boolean" }).notNull().default(false), sortOrder: int("sort_order", { mode: "number" }).notNull().default(0), - createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`), - updatedAt: int("updated_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`), + createdAt: int("created_at", { mode: "number" }) + .notNull() + .default(sql`(unixepoch() * 1000)`), + updatedAt: int("updated_at", { mode: "number" }) + .notNull() + .default(sql`(unixepoch() * 1000)`), }); export type BackupScheduleInsert = typeof backupSchedulesTable.$inferInsert; @@ -125,8 +228,12 @@ export const notificationDestinationsTable = sqliteTable("notification_destinati enabled: int("enabled", { mode: "boolean" }).notNull().default(true), type: text().$type().notNull(), config: text("config", { mode: "json" }).$type().notNull(), - createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`), - updatedAt: int("updated_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`), + createdAt: int("created_at", { mode: "number" }) + .notNull() + .default(sql`(unixepoch() * 1000)`), + updatedAt: int("updated_at", { mode: "number" }) + .notNull() + .default(sql`(unixepoch() * 1000)`), }); export const notificationDestinationRelations = relations(notificationDestinationsTable, ({ many }) => ({ schedules: many(backupScheduleNotificationsTable), @@ -149,7 +256,9 @@ export const backupScheduleNotificationsTable = sqliteTable( notifyOnSuccess: int("notify_on_success", { mode: "boolean" }).notNull().default(false), notifyOnWarning: int("notify_on_warning", { mode: "boolean" }).notNull().default(true), notifyOnFailure: int("notify_on_failure", { mode: "boolean" }).notNull().default(true), - createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`), + createdAt: int("created_at", { mode: "number" }) + .notNull() + .default(sql`(unixepoch() * 1000)`), }, (table) => [primaryKey({ columns: [table.scheduleId, table.destinationId] })], ); @@ -183,7 +292,9 @@ export const backupScheduleMirrorsTable = sqliteTable( lastCopyAt: int("last_copy_at", { mode: "number" }), lastCopyStatus: text("last_copy_status").$type<"success" | "error">(), lastCopyError: text("last_copy_error"), - createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`), + createdAt: int("created_at", { mode: "number" }) + .notNull() + .default(sql`(unixepoch() * 1000)`), }, (table) => [unique().on(table.scheduleId, table.repositoryId)], ); @@ -207,7 +318,11 @@ export type BackupScheduleMirror = typeof backupScheduleMirrorsTable.$inferSelec export const appMetadataTable = sqliteTable("app_metadata", { key: text().primaryKey(), value: text().notNull(), - createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`), - updatedAt: int("updated_at", { mode: "number" }).notNull().default(sql`(unixepoch() * 1000)`), + createdAt: int("created_at", { mode: "number" }) + .notNull() + .default(sql`(unixepoch() * 1000)`), + updatedAt: int("updated_at", { mode: "number" }) + .notNull() + .default(sql`(unixepoch() * 1000)`), }); export type AppMetadata = typeof appMetadataTable.$inferSelect; diff --git a/app/server/index.ts b/app/server/index.ts index 52beea7c..f2c51b6c 100644 --- a/app/server/index.ts +++ b/app/server/index.ts @@ -1,5 +1,6 @@ import { createHonoServer } from "react-router-hono-server/bun"; -import { runDbMigrations } from "./db/db"; +import * as schema from "./db/schema"; +import { setSchema, runDbMigrations } from "./db/db"; import { startup } from "./modules/lifecycle/startup"; import { retagSnapshots } from "./modules/lifecycle/migration"; import { logger } from "./utils/logger"; @@ -10,6 +11,8 @@ import { createApp } from "./app"; import { config } from "./core/config"; import { runCLI } from "./cli"; +setSchema(schema); + const cliRun = await runCLI(Bun.argv); if (cliRun) { process.exit(0); diff --git a/app/server/jobs/cleanup-sessions.ts b/app/server/jobs/cleanup-sessions.ts deleted file mode 100644 index e457f1ce..00000000 --- a/app/server/jobs/cleanup-sessions.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Job } from "../core/scheduler"; -import { authService } from "../modules/auth/auth.service"; - -export class CleanupSessionsJob extends Job { - async run() { - await authService.cleanupExpiredSessions(); - - return { done: true, timestamp: new Date() }; - } -} diff --git a/app/server/modules/auth/auth.controller.ts b/app/server/modules/auth/auth.controller.ts index 45ba5518..050e22ce 100644 --- a/app/server/modules/auth/auth.controller.ts +++ b/app/server/modules/auth/auth.controller.ts @@ -1,157 +1,8 @@ -import { validator } from "hono-openapi"; -import { rateLimiter } from "hono-rate-limiter"; import { Hono } from "hono"; -import { deleteCookie, getCookie, setCookie } from "hono/cookie"; -import { - changePasswordBodySchema, - changePasswordDto, - getMeDto, - getStatusDto, - loginBodySchema, - loginDto, - logoutDto, - registerBodySchema, - registerDto, - type ChangePasswordDto, - type GetMeDto, - type GetStatusDto, - type LoginDto, - type LogoutDto, - type RegisterDto, -} from "./auth.dto"; +import { getStatusDto, type GetStatusDto } from "./auth.dto"; import { authService } from "./auth.service"; -import { toMessage } from "../../utils/errors"; -import { config } from "~/server/core/config"; -const COOKIE_NAME = "session_id"; -const COOKIE_OPTIONS = { - httpOnly: true, - secure: false, - sameSite: "lax" as const, - path: "/", -}; - -const authRateLimiter = rateLimiter({ - windowMs: 15 * 60 * 1000, - limit: 20, - keyGenerator: (c) => c.req.header("x-forwarded-for") ?? "", - skip: () => { - return config.__prod__ === false; - }, +export const authController = new Hono().get("/status", getStatusDto, async (c) => { + const hasUsers = await authService.hasUsers(); + return c.json({ hasUsers }); }); - -export const authController = new Hono() - .post("/register", authRateLimiter, registerDto, validator("json", registerBodySchema), async (c) => { - const body = c.req.valid("json"); - - try { - const { user, sessionId } = await authService.register(body.username, body.password); - - setCookie(c, COOKIE_NAME, sessionId, { - ...COOKIE_OPTIONS, - expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days - }); - - return c.json( - { - success: true, - message: "User registered successfully", - user: { - id: user.id, - username: user.username, - hasDownloadedResticPassword: user.hasDownloadedResticPassword, - }, - }, - 201, - ); - } catch (error) { - return c.json({ success: false, message: toMessage(error) }, 400); - } - }) - .post("/login", authRateLimiter, loginDto, validator("json", loginBodySchema), async (c) => { - const body = c.req.valid("json"); - - try { - const { sessionId, user, expiresAt } = await authService.login(body.username, body.password); - - setCookie(c, COOKIE_NAME, sessionId, { - ...COOKIE_OPTIONS, - expires: new Date(expiresAt), - }); - - return c.json({ - success: true, - message: "Login successful", - user: { - id: user.id, - username: user.username, - hasDownloadedResticPassword: user.hasDownloadedResticPassword, - }, - }); - } catch (error) { - return c.json({ success: false, message: toMessage(error) }, 401); - } - }) - .post("/logout", authRateLimiter, logoutDto, async (c) => { - const sessionId = getCookie(c, COOKIE_NAME); - - if (sessionId) { - await authService.logout(sessionId); - deleteCookie(c, COOKIE_NAME, COOKIE_OPTIONS); - } - - return c.json({ success: true }); - }) - .get("/me", getMeDto, async (c) => { - const sessionId = getCookie(c, COOKIE_NAME); - - if (!sessionId) { - return c.json({ success: false, message: "Not authenticated" }, 401); - } - - const session = await authService.verifySession(sessionId); - - if (!session) { - deleteCookie(c, COOKIE_NAME, COOKIE_OPTIONS); - return c.json({ message: "Not authenticated" }, 401); - } - - return c.json({ - success: true, - user: session.user, - message: "Authenticated", - }); - }) - .get("/status", getStatusDto, async (c) => { - const hasUsers = await authService.hasUsers(); - return c.json({ hasUsers }); - }) - .post( - "/change-password", - authRateLimiter, - changePasswordDto, - validator("json", changePasswordBodySchema), - async (c) => { - const sessionId = getCookie(c, COOKIE_NAME); - - if (!sessionId) { - return c.json({ success: false, message: "Not authenticated" }, 401); - } - - const session = await authService.verifySession(sessionId); - - if (!session) { - deleteCookie(c, COOKIE_NAME, COOKIE_OPTIONS); - return c.json({ success: false, message: "Not authenticated" }, 401); - } - - const body = c.req.valid("json"); - - try { - await authService.changePassword(session.user.id, body.currentPassword, body.newPassword); - return c.json({ success: true, message: "Password changed successfully" }); - } catch (error) { - return c.json({ success: false, message: toMessage(error) }, 400); - } - }, - ); diff --git a/app/server/modules/auth/auth.dto.ts b/app/server/modules/auth/auth.dto.ts index f305f9c1..6033ebe5 100644 --- a/app/server/modules/auth/auth.dto.ts +++ b/app/server/modules/auth/auth.dto.ts @@ -1,103 +1,6 @@ import { type } from "arktype"; import { describeRoute, resolver } from "hono-openapi"; -// Validation schemas -export const loginBodySchema = type({ - username: "string>0", - password: "string>7", -}); - -export const registerBodySchema = type({ - username: "string>2", - password: "string>7", -}); - -const loginResponseSchema = type({ - message: "string", - success: "boolean", - user: type({ - id: "number", - username: "string", - hasDownloadedResticPassword: "boolean", - }).optional(), -}); - -export const loginDto = describeRoute({ - description: "Login with username and password", - operationId: "login", - tags: ["Auth"], - responses: { - 200: { - description: "Login successful", - content: { - "application/json": { - schema: resolver(loginResponseSchema), - }, - }, - }, - }, -}); - -export type LoginDto = typeof loginResponseSchema.infer; - -export const registerDto = describeRoute({ - description: "Register a new user", - operationId: "register", - tags: ["Auth"], - responses: { - 201: { - description: "User created successfully", - content: { - "application/json": { - schema: resolver(loginResponseSchema), - }, - }, - }, - }, -}); - -export type RegisterDto = typeof loginResponseSchema.infer; - -const logoutResponseSchema = type({ - success: "boolean", -}); - -export const logoutDto = describeRoute({ - description: "Logout current user", - operationId: "logout", - tags: ["Auth"], - responses: { - 200: { - description: "Logout successful", - content: { - "application/json": { - schema: resolver(logoutResponseSchema), - }, - }, - }, - }, -}); - -export type LogoutDto = typeof logoutResponseSchema.infer; - -export const getMeDto = describeRoute({ - description: "Get current authenticated user", - operationId: "getMe", - tags: ["Auth"], - responses: { - 200: { - description: "Current user information", - content: { - "application/json": { - schema: resolver(loginResponseSchema), - }, - }, - }, - }, -}); - -export type GetMeDto = typeof loginResponseSchema.infer; - const statusResponseSchema = type({ hasUsers: "boolean", }); @@ -119,35 +22,3 @@ export const getStatusDto = describeRoute({ }); export type GetStatusDto = typeof statusResponseSchema.infer; - -export const changePasswordBodySchema = type({ - currentPassword: "string>0", - newPassword: "string>7", -}); - -const changePasswordResponseSchema = type({ - success: "boolean", - message: "string", -}); - -export const changePasswordDto = describeRoute({ - description: "Change current user password", - operationId: "changePassword", - tags: ["Auth"], - responses: { - 200: { - description: "Password changed successfully", - content: { - "application/json": { - schema: resolver(changePasswordResponseSchema), - }, - }, - }, - }, -}); - -export type ChangePasswordDto = typeof changePasswordResponseSchema.infer; - -export type LoginBody = typeof loginBodySchema.infer; -export type RegisterBody = typeof registerBodySchema.infer; -export type ChangePasswordBody = typeof changePasswordBodySchema.infer; diff --git a/app/server/modules/auth/auth.middleware.ts b/app/server/modules/auth/auth.middleware.ts index f94654e9..a9a211c0 100644 --- a/app/server/modules/auth/auth.middleware.ts +++ b/app/server/modules/auth/auth.middleware.ts @@ -1,19 +1,10 @@ -import { deleteCookie, getCookie } from "hono/cookie"; import { createMiddleware } from "hono/factory"; -import { authService } from "./auth.service"; - -const COOKIE_NAME = "session_id"; -const COOKIE_OPTIONS = { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "lax" as const, - path: "/", -}; +import { auth } from "~/lib/auth"; declare module "hono" { interface ContextVariableMap { user: { - id: number; + id: string; username: string; hasDownloadedResticPassword: boolean; }; @@ -25,40 +16,17 @@ declare module "hono" { * Verifies the session cookie and attaches user to context */ export const requireAuth = createMiddleware(async (c, next) => { - const sessionId = getCookie(c, COOKIE_NAME); + const session = await auth.api.getSession({ + headers: c.req.raw.headers, + }); - if (!sessionId) { - return c.json({ message: "Authentication required" }, 401); + const { user } = session ?? {}; + + if (!user) { + return c.json({ message: "Invalid or expired session" }, 401); } - const session = await authService.verifySession(sessionId); - - if (!session) { - deleteCookie(c, COOKIE_NAME, COOKIE_OPTIONS); - return c.json({ message: "Invalid or expired session" }, 401); - } - - c.set("user", session.user); - - await next(); -}); - -/** - * Middleware to optionally attach user if authenticated - * Does not block the request if not authenticated - */ -export const optionalAuth = createMiddleware(async (c, next) => { - const sessionId = getCookie(c, COOKIE_NAME); - - if (sessionId) { - const session = await authService.verifySession(sessionId); - - if (session) { - c.set("user", session.user); - } else { - deleteCookie(c, COOKIE_NAME, COOKIE_OPTIONS); - } - } + c.set("user", user); await next(); }); diff --git a/app/server/modules/auth/auth.service.ts b/app/server/modules/auth/auth.service.ts index 4a97c0df..06e0efd1 100644 --- a/app/server/modules/auth/auth.service.ts +++ b/app/server/modules/auth/auth.service.ts @@ -1,145 +1,7 @@ -import { eq, lt } from "drizzle-orm"; import { db } from "../../db/db"; -import { sessionsTable, usersTable } from "../../db/schema"; -import { logger } from "../../utils/logger"; - -const SESSION_DURATION = 60 * 60 * 24 * 30 * 1000; // 30 days in milliseconds +import { usersTable } from "../../db/schema"; export class AuthService { - /** - * Register a new user with username and password - */ - async register(username: string, password: string) { - const [existingUser] = await db.select().from(usersTable); - - if (existingUser) { - throw new Error("Admin user already exists"); - } - - const passwordHash = await Bun.password.hash(password, { - algorithm: "argon2id", - memoryCost: 19456, - timeCost: 2, - }); - - const [user] = await db.insert(usersTable).values({ username, passwordHash }).returning(); - - if (!user) { - throw new Error("User registration failed"); - } - - logger.info(`User registered: ${username}`); - const sessionId = crypto.randomUUID(); - const expiresAt = Date.now() + SESSION_DURATION; - - await db.insert(sessionsTable).values({ - id: sessionId, - userId: user.id, - expiresAt, - }); - - return { - user: { - id: user.id, - username: user.username, - createdAt: user.createdAt, - hasDownloadedResticPassword: user.hasDownloadedResticPassword, - }, - sessionId, - }; - } - - /** - * Login user with username and password - */ - async login(username: string, password: string) { - const [user] = await db.select().from(usersTable).where(eq(usersTable.username, username)); - - if (!user) { - throw new Error("Invalid credentials"); - } - - const isValid = await Bun.password.verify(password, user.passwordHash); - - if (!isValid) { - throw new Error("Invalid credentials"); - } - - const sessionId = crypto.randomUUID(); - const expiresAt = Date.now() + SESSION_DURATION; - - await db.insert(sessionsTable).values({ - id: sessionId, - userId: user.id, - expiresAt, - }); - - logger.info(`User logged in: ${username}`); - - return { - sessionId, - user: { - id: user.id, - username: user.username, - hasDownloadedResticPassword: user.hasDownloadedResticPassword, - }, - expiresAt, - }; - } - - /** - * Logout user by deleting their session - */ - async logout(sessionId: string) { - await db.delete(sessionsTable).where(eq(sessionsTable.id, sessionId)); - logger.info(`User logged out: session ${sessionId}`); - } - - /** - * Verify a session and return the associated user - */ - async verifySession(sessionId: string) { - const [session] = await db - .select({ - session: sessionsTable, - user: usersTable, - }) - .from(sessionsTable) - .innerJoin(usersTable, eq(sessionsTable.userId, usersTable.id)) - .where(eq(sessionsTable.id, sessionId)); - - if (!session) { - return null; - } - - if (session.session.expiresAt < Date.now()) { - await db.delete(sessionsTable).where(eq(sessionsTable.id, sessionId)); - return null; - } - - return { - user: { - id: session.user.id, - username: session.user.username, - hasDownloadedResticPassword: session.user.hasDownloadedResticPassword, - }, - session: { - id: session.session.id, - expiresAt: session.session.expiresAt, - }, - }; - } - - /** - * Clean up expired sessions - */ - async cleanupExpiredSessions() { - const result = await db.delete(sessionsTable).where(lt(sessionsTable.expiresAt, Date.now())).returning(); - if (result.length > 0) { - logger.info(`Cleaned up ${result.length} expired sessions`); - } - } - /** * Check if any users exist in the system */ @@ -147,33 +9,6 @@ export class AuthService { const [user] = await db.select({ id: usersTable.id }).from(usersTable).limit(1); return !!user; } - - /** - * Change password for a user - */ - async changePassword(userId: number, currentPassword: string, newPassword: string) { - const [user] = await db.select().from(usersTable).where(eq(usersTable.id, userId)); - - if (!user) { - throw new Error("User not found"); - } - - const isValid = await Bun.password.verify(currentPassword, user.passwordHash); - - if (!isValid) { - throw new Error("Current password is incorrect"); - } - - const newPasswordHash = await Bun.password.hash(newPassword, { - algorithm: "argon2id", - memoryCost: 19456, - timeCost: 2, - }); - - await db.update(usersTable).set({ passwordHash: newPasswordHash }).where(eq(usersTable.id, userId)); - - logger.info(`Password changed for user: ${user.username}`); - } } export const authService = new AuthService(); diff --git a/app/server/modules/auth/helpers.ts b/app/server/modules/auth/helpers.ts new file mode 100644 index 00000000..f5e31470 --- /dev/null +++ b/app/server/modules/auth/helpers.ts @@ -0,0 +1,26 @@ +import { verifyPassword } from "better-auth/crypto"; +import { eq } from "drizzle-orm"; +import { db } from "~/server/db/db"; +import { account } from "~/server/db/schema"; + +type PasswordVerificationBody = { + userId: string; + password: string; +}; + +export const verifyUserPassword = async ({ password, userId }: PasswordVerificationBody) => { + const userAccount = await db.query.account.findFirst({ + where: eq(account.userId, userId), + }); + + if (!userAccount || !userAccount.password) { + return false; + } + + const isPasswordValid = await verifyPassword({ password: password, hash: userAccount.password }); + if (!isPasswordValid) { + return false; + } + + return true; +}; diff --git a/app/server/modules/backups/__tests__/backups.controller.test.ts b/app/server/modules/backups/__tests__/backups.controller.test.ts index 3c415004..fc7eda26 100644 --- a/app/server/modules/backups/__tests__/backups.controller.test.ts +++ b/app/server/modules/backups/__tests__/backups.controller.test.ts @@ -9,28 +9,26 @@ describe("backups security", () => { const res = await app.request("/api/v1/backups"); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); test("should return 401 if session is invalid", async () => { const res = await app.request("/api/v1/backups", { headers: { - Cookie: "session_id=invalid-session", + Cookie: "better-auth.session_token=invalid-session", }, }); expect(res.status).toBe(401); const body = await res.json(); expect(body.message).toBe("Invalid or expired session"); - - expect(res.headers.get("Set-Cookie")).toContain("session_id=;"); }); test("should return 200 if session is valid", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/backups", { headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, }, }); @@ -61,7 +59,7 @@ describe("backups security", () => { const res = await app.request(path, { method }); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); } }); @@ -71,23 +69,23 @@ describe("backups security", () => { const res = await app.request("/api/v1/backups/999999"); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); test("should not disclose if a volume exists when unauthenticated", async () => { const res = await app.request("/api/v1/backups/volume/999999"); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); }); describe("input validation", () => { test("should return 404 for malformed schedule ID", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/backups/not-a-number", { headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, }, }); @@ -95,10 +93,10 @@ describe("backups security", () => { }); test("should return 404 for non-existent schedule ID", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/backups/999999", { headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, }, }); @@ -108,11 +106,11 @@ describe("backups security", () => { }); test("should return 400 for invalid payload on create", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/backups", { method: "POST", headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ diff --git a/app/server/modules/events/__tests__/events.controller.test.ts b/app/server/modules/events/__tests__/events.controller.test.ts index c40793fe..1121438d 100644 --- a/app/server/modules/events/__tests__/events.controller.test.ts +++ b/app/server/modules/events/__tests__/events.controller.test.ts @@ -9,28 +9,26 @@ describe("events security", () => { const res = await app.request("/api/v1/events"); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); test("should return 401 if session is invalid", async () => { const res = await app.request("/api/v1/events", { headers: { - Cookie: "session_id=invalid-session", + Cookie: "better-auth.session_token=invalid-session", }, }); expect(res.status).toBe(401); const body = await res.json(); expect(body.message).toBe("Invalid or expired session"); - - expect(res.headers.get("Set-Cookie")).toContain("session_id=;"); }); test("should return 200 if session is valid", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/events", { headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, }, }); @@ -46,7 +44,7 @@ describe("events security", () => { const res = await app.request(path, { method }); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); } }); diff --git a/app/server/modules/lifecycle/startup.ts b/app/server/modules/lifecycle/startup.ts index f13d14c8..9d18aaa4 100644 --- a/app/server/modules/lifecycle/startup.ts +++ b/app/server/modules/lifecycle/startup.ts @@ -9,7 +9,6 @@ import { CleanupDanglingMountsJob } from "../../jobs/cleanup-dangling"; import { VolumeHealthCheckJob } from "../../jobs/healthchecks"; import { RepositoryHealthCheckJob } from "../../jobs/repository-healthchecks"; import { BackupExecutionJob } from "../../jobs/backup-execution"; -import { CleanupSessionsJob } from "../../jobs/cleanup-sessions"; import { repositoriesService } from "../repositories/repositories.service"; import { notificationsService } from "../notifications/notifications.service"; import { VolumeAutoRemountJob } from "~/server/jobs/auto-remount"; @@ -82,6 +81,5 @@ export const startup = async () => { Scheduler.build(VolumeHealthCheckJob).schedule("*/30 * * * *"); Scheduler.build(RepositoryHealthCheckJob).schedule("50 12 * * *"); Scheduler.build(BackupExecutionJob).schedule("* * * * *"); - Scheduler.build(CleanupSessionsJob).schedule("0 0 * * *"); Scheduler.build(VolumeAutoRemountJob).schedule("*/5 * * * *"); }; diff --git a/app/server/modules/notifications/__tests__/notifications.controller.test.ts b/app/server/modules/notifications/__tests__/notifications.controller.test.ts index 02834267..0c24b657 100644 --- a/app/server/modules/notifications/__tests__/notifications.controller.test.ts +++ b/app/server/modules/notifications/__tests__/notifications.controller.test.ts @@ -9,28 +9,26 @@ describe("notifications security", () => { const res = await app.request("/api/v1/notifications/destinations"); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); test("should return 401 if session is invalid", async () => { const res = await app.request("/api/v1/notifications/destinations", { headers: { - Cookie: "session_id=invalid-session", + Cookie: "better-auth.session_token=invalid-session", }, }); expect(res.status).toBe(401); const body = await res.json(); expect(body.message).toBe("Invalid or expired session"); - - expect(res.headers.get("Set-Cookie")).toContain("session_id=;"); }); test("should return 200 if session is valid", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/notifications/destinations", { headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, }, }); @@ -52,7 +50,7 @@ describe("notifications security", () => { const res = await app.request(path, { method }); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); } }); @@ -62,16 +60,16 @@ describe("notifications security", () => { const res = await app.request("/api/v1/notifications/destinations/999999"); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); }); describe("input validation", () => { test("should return 404 for malformed destination ID", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/notifications/destinations/not-a-number", { headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, }, }); @@ -79,10 +77,10 @@ describe("notifications security", () => { }); test("should return 404 for non-existent destination ID", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/notifications/destinations/999999", { headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, }, }); @@ -92,11 +90,12 @@ describe("notifications security", () => { }); test("should return 400 for invalid payload on create", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); + const res = await app.request("/api/v1/notifications/destinations", { method: "POST", headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ diff --git a/app/server/modules/repositories/__tests__/repositories.controller.test.ts b/app/server/modules/repositories/__tests__/repositories.controller.test.ts index 3f375df4..89b5d7e7 100644 --- a/app/server/modules/repositories/__tests__/repositories.controller.test.ts +++ b/app/server/modules/repositories/__tests__/repositories.controller.test.ts @@ -9,28 +9,26 @@ describe("repositories security", () => { const res = await app.request("/api/v1/repositories"); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); test("should return 401 if session is invalid", async () => { const res = await app.request("/api/v1/repositories", { headers: { - Cookie: "session_id=invalid-session", + Cookie: "better-auth.session_token=invalid-session", }, }); expect(res.status).toBe(401); const body = await res.json(); expect(body.message).toBe("Invalid or expired session"); - - expect(res.headers.get("Set-Cookie")).toContain("session_id=;"); }); test("should return 200 if session is valid", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/repositories", { headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, }, }); @@ -59,7 +57,7 @@ describe("repositories security", () => { const res = await app.request(path, { method }); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); } }); @@ -69,16 +67,16 @@ describe("repositories security", () => { const res = await app.request("/api/v1/repositories/non-existent-repo"); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); }); describe("input validation", () => { test("should return 404 for non-existent repository", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/repositories/non-existent-repo", { headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, }, }); @@ -88,11 +86,11 @@ describe("repositories security", () => { }); test("should return 400 for invalid payload on create", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/repositories", { method: "POST", headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ diff --git a/app/server/modules/system/__tests__/system.controller.test.ts b/app/server/modules/system/__tests__/system.controller.test.ts index 5ce3d51e..5aa5ad81 100644 --- a/app/server/modules/system/__tests__/system.controller.test.ts +++ b/app/server/modules/system/__tests__/system.controller.test.ts @@ -9,28 +9,26 @@ describe("system security", () => { const res = await app.request("/api/v1/system/info"); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); test("should return 401 if session is invalid", async () => { const res = await app.request("/api/v1/system/info", { headers: { - Cookie: "session_id=invalid-session", + Cookie: "better-auth.session_token=invalid-session", }, }); expect(res.status).toBe(401); const body = await res.json(); expect(body.message).toBe("Invalid or expired session"); - - expect(res.headers.get("Set-Cookie")).toContain("session_id=;"); }); test("should return 200 if session is valid", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/system/info", { headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, }, }); @@ -48,18 +46,18 @@ describe("system security", () => { const res = await app.request(path, { method }); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); } }); describe("input validation", () => { test("should return 400 for invalid payload on restic-password", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/system/restic-password", { method: "POST", headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, "Content-Type": "application/json", }, body: JSON.stringify({}), @@ -69,11 +67,11 @@ describe("system security", () => { }); test("should return 401 for incorrect password on restic-password", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/system/restic-password", { method: "POST", headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ @@ -83,7 +81,7 @@ describe("system security", () => { expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Incorrect password"); + expect(body.message).toBe("Invalid password"); }); }); }); diff --git a/app/server/modules/system/system.controller.ts b/app/server/modules/system/system.controller.ts index 7e5aec7d..326fe33f 100644 --- a/app/server/modules/system/system.controller.ts +++ b/app/server/modules/system/system.controller.ts @@ -14,6 +14,7 @@ import { RESTIC_PASS_FILE } from "../../core/constants"; import { db } from "../../db/db"; import { usersTable } from "../../db/schema"; import { eq } from "drizzle-orm"; +import { verifyUserPassword } from "../auth/helpers"; export const systemController = new Hono() .use(requireAuth) @@ -35,16 +36,9 @@ export const systemController = new Hono() const user = c.get("user"); const body = c.req.valid("json"); - const [dbUser] = await db.select().from(usersTable).where(eq(usersTable.id, user.id)); - - if (!dbUser) { - return c.json({ message: "User not found" }, 401); - } - - const isValid = await Bun.password.verify(body.password, dbUser.passwordHash); - - if (!isValid) { - return c.json({ message: "Incorrect password" }, 401); + const isPasswordValid = await verifyUserPassword({ password: body.password, userId: user.id }); + if (!isPasswordValid) { + return c.json({ message: "Invalid password" }, 401); } try { diff --git a/app/server/modules/volumes/__tests__/volumes.controller.test.ts b/app/server/modules/volumes/__tests__/volumes.controller.test.ts index 04b05776..1acb13c4 100644 --- a/app/server/modules/volumes/__tests__/volumes.controller.test.ts +++ b/app/server/modules/volumes/__tests__/volumes.controller.test.ts @@ -9,28 +9,26 @@ describe("volumes security", () => { const res = await app.request("/api/v1/volumes"); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); test("should return 401 if session is invalid", async () => { const res = await app.request("/api/v1/volumes", { headers: { - Cookie: "session_id=invalid-session", + Cookie: "better-auth.session_token=invalid-session", }, }); expect(res.status).toBe(401); const body = await res.json(); expect(body.message).toBe("Invalid or expired session"); - - expect(res.headers.get("Set-Cookie")).toContain("session_id=;"); }); test("should return 200 if session is valid", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/volumes", { headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, }, }); @@ -57,7 +55,7 @@ describe("volumes security", () => { const res = await app.request(path, { method }); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); } }); @@ -67,16 +65,16 @@ describe("volumes security", () => { const res = await app.request("/api/v1/volumes/non-existent-volume"); expect(res.status).toBe(401); const body = await res.json(); - expect(body.message).toBe("Authentication required"); + expect(body.message).toBe("Invalid or expired session"); }); }); describe("input validation", () => { test("should return 404 for non-existent volume", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/volumes/non-existent-volume", { headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, }, }); @@ -86,11 +84,11 @@ describe("volumes security", () => { }); test("should return 400 for invalid payload on create", async () => { - const { sessionId } = await createTestSession(); + const { token } = await createTestSession(); const res = await app.request("/api/v1/volumes", { method: "POST", headers: { - Cookie: `session_id=${sessionId}`, + Cookie: `better-auth.session_token=${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ diff --git a/app/server/utils/crypto.ts b/app/server/utils/crypto.ts index 34c3af98..fb86a7e3 100644 --- a/app/server/utils/crypto.ts +++ b/app/server/utils/crypto.ts @@ -3,6 +3,9 @@ import fs from "node:fs/promises"; import path from "node:path"; import { RESTIC_PASS_FILE } from "../core/constants"; import { isNodeJSErrnoException } from "./fs"; +import { promisify } from "node:util"; + +const hkdf = promisify(crypto.hkdf); const algorithm = "aes-256-gcm" as const; const keyLength = 32; @@ -183,7 +186,16 @@ const sealSecret = async (value: string): Promise => { return encrypt(value); }; +async function deriveSecret(label: string) { + const masterSecret = await Bun.file(RESTIC_PASS_FILE).text(); + + const derivedKey = await hkdf("sha256", masterSecret, "", label, 32); + + return Buffer.from(derivedKey).toString("hex"); +} + export const cryptoUtils = { resolveSecret, sealSecret, + deriveSecret, }; diff --git a/app/test/helpers/auth.ts b/app/test/helpers/auth.ts index fba04ccf..b8e7dadd 100644 --- a/app/test/helpers/auth.ts +++ b/app/test/helpers/auth.ts @@ -1,24 +1,53 @@ -import { authService } from "~/server/modules/auth/auth.service"; import { db } from "~/server/db/db"; -import { usersTable, sessionsTable } from "~/server/db/schema"; +import { sessionsTable, usersTable, account } from "~/server/db/schema"; +import { hashPassword } from "better-auth/crypto"; +import { createHmac } from "node:crypto"; export async function createTestSession() { const [existingUser] = await db.select().from(usersTable); if (!existingUser) { - await authService.register("testadmin", "testpassword"); + await db.insert(usersTable).values({ + username: "testuser", + email: "test@test.com", + name: "Test User", + id: crypto.randomUUID(), + }); } const [user] = await db.select().from(usersTable); - const sessionId = crypto.randomUUID(); - const expiresAt = Date.now() + 1000 * 60 * 60 * 24; // 24 hours + const token = crypto.randomUUID().replace(/-/g, ""); + const sessionId = token; + const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); await db.insert(sessionsTable).values({ id: sessionId, userId: user.id, expiresAt, + token: token, + createdAt: new Date(), + updatedAt: new Date(), }); - return { sessionId, user }; + // Better Auth signs the token using HMAC-SHA256 with the secret + // The secret is "test-secret" because we mocked cryptoUtils.deriveSecret + const signature = createHmac("sha256", "test-secret").update(token).digest("base64"); + + const signedToken = `${token}.${signature}`; + + await db + .insert(account) + .values({ + userId: user.id, + accountId: "testuser", + password: await hashPassword("password123"), + id: crypto.randomUUID(), + providerId: "credentials", + createdAt: new Date(), + updatedAt: new Date(), + }) + .onConflictDoNothing(); + + return { token: encodeURIComponent(signedToken), user }; } diff --git a/app/test/setup.ts b/app/test/setup.ts index 43360902..81927747 100644 --- a/app/test/setup.ts +++ b/app/test/setup.ts @@ -2,7 +2,10 @@ import { beforeAll, mock } from "bun:test"; import { migrate } from "drizzle-orm/bun-sqlite/migrator"; import path from "node:path"; import { cwd } from "node:process"; -import { db } from "~/server/db/db"; +import * as schema from "~/server/db/schema"; +import { db, setSchema } from "~/server/db/db"; + +setSchema(schema); void mock.module("~/server/utils/logger", () => ({ logger: { @@ -13,6 +16,14 @@ void mock.module("~/server/utils/logger", () => ({ }, })); +void mock.module("~/server/utils/crypto", () => ({ + cryptoUtils: { + deriveSecret: async () => "test-secret", + sealSecret: async (v: string) => v, + resolveSecret: async (v: string) => v, + }, +})); + beforeAll(async () => { const migrationsFolder = path.join(cwd(), "app", "drizzle"); migrate(db, { migrationsFolder }); diff --git a/bun.lock b/bun.lock index b7fb5e1b..3f8b1409 100644 --- a/bun.lock +++ b/bun.lock @@ -30,6 +30,7 @@ "@scalar/hono-api-reference": "^0.9.25", "@tanstack/react-query": "^5.90.11", "arktype": "^2.1.28", + "better-auth": "^1.4.10", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "commander": "^14.0.2", @@ -160,6 +161,14 @@ "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], + "@better-auth/core": ["@better-auth/core@1.4.10", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "zod": "^4.1.12" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "better-call": "1.1.7", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-AThrfb6CpG80wqkanfrbN2/fGOYzhGladHFf3JhaWt/3/Vtf4h084T6PJLrDE7M/vCCGYvDI1DkvP3P1OB2HAg=="], + + "@better-auth/telemetry": ["@better-auth/telemetry@1.4.10", "", { "dependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21" }, "peerDependencies": { "@better-auth/core": "1.4.10" } }, "sha512-Dq4XJX6EKsUu0h3jpRagX739p/VMOTcnJYWRrLtDYkqtZFg+sFiFsSWVcfapZoWpRSUGYX9iKwl6nDHn6Ju2oQ=="], + + "@better-auth/utils": ["@better-auth/utils@0.3.0", "", {}, "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw=="], + + "@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="], + "@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="], "@dabh/diagnostics": ["@dabh/diagnostics@2.0.8", "", { "dependencies": { "@so-ric/colorspace": "^1.1.6", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q=="], @@ -304,6 +313,10 @@ "@mjackson/node-fetch-server": ["@mjackson/node-fetch-server@0.2.0", "", {}, "sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng=="], + "@noble/ciphers": ["@noble/ciphers@2.1.1", "", {}, "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw=="], + + "@noble/hashes": ["@noble/hashes@2.0.1", "", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="], + "@oxfmt/darwin-arm64": ["@oxfmt/darwin-arm64@0.22.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-dhz2m2uLrHT3MwM+LAdvr97EojJZTwaZ6BuMTRftJzqa9dHYDG/MtSBuDD2DpGpZ0SM2iVwni2wCzCYGKTojbA=="], "@oxfmt/darwin-x64": ["@oxfmt/darwin-x64@0.22.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-VykUbibvqSOG5YIFUMpHtZVrY1YKDl9Il2SvFemUfR5Ac1t1BFZOnazYe98jtZGFY4sEdEORs0ImBARnyMX/hw=="], @@ -348,6 +361,8 @@ "@oxlint/win32-x64": ["@oxlint/win32-x64@1.36.0", "", { "os": "win32", "cpu": "x64" }, "sha512-J+Vc00Utcf8p77lZPruQgb0QnQXuKnFogN88kCnOqs2a83I+vTBB8ILr0+L9sTwVRvIDMSC0pLdeQH4svWGFZg=="], + "@prisma/client": ["@prisma/client@5.22.0", "", { "peerDependencies": { "prisma": "*" }, "optionalPeers": ["prisma"] }, "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA=="], + "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], @@ -584,6 +599,8 @@ "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="], + "@types/pg": ["@types/pg@8.16.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ=="], + "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], @@ -632,16 +649,30 @@ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.9.11", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ=="], "basic-auth": ["basic-auth@2.0.1", "", { "dependencies": { "safe-buffer": "5.1.2" } }, "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg=="], + "better-auth": ["better-auth@1.4.10", "", { "dependencies": { "@better-auth/core": "1.4.10", "@better-auth/telemetry": "1.4.10", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "better-call": "1.1.7", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.1.12" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": ">=0.41.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-0kqwEBJLe8eyFzbUspRG/htOriCf9uMLlnpe34dlIJGdmDfPuQISd4shShvUrvIVhPxsY1dSTXdXPLpqISYOYg=="], + + "better-call": ["better-call@1.1.7", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-6gaJe1bBIEgVebQu/7q9saahVzvBsGaByEnE8aDVncZEDiJO7sdNB28ot9I6iXSbR25egGmmZ6aIURXyQHRraQ=="], + + "better-sqlite3": ["better-sqlite3@12.5.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg=="], + + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + "body-parser": ["body-parser@1.20.4", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.14.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA=="], "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="], @@ -674,6 +705,8 @@ "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], @@ -756,8 +789,12 @@ "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + "dedent": ["dedent@1.7.1", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg=="], + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + "default-browser": ["default-browser@5.4.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg=="], "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], @@ -806,6 +843,8 @@ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="], "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], @@ -836,6 +875,8 @@ "exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="], + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + "express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="], "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], @@ -846,6 +887,8 @@ "fecha": ["fecha@4.2.3", "", {}, "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="], + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], + "finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="], "fn.name": ["fn.name@1.1.0", "", {}, "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="], @@ -854,6 +897,8 @@ "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -874,6 +919,8 @@ "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + "globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="], "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], @@ -906,10 +953,14 @@ "iconv-lite": ["iconv-lite@0.7.1", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "immer": ["immer@10.2.0", "", {}, "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], @@ -944,6 +995,8 @@ "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], @@ -954,6 +1007,8 @@ "kuler": ["kuler@2.0.0", "", {}, "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="], + "kysely": ["kysely@0.28.9", "", {}, "sha512-3BeXMoiOhpOwu62CiVpO6lxfq4eS6KMYfQdMsN/2kUCRNuF2YiEr7u0HLHaQU+O4Xu8YXE3bHVkwaQ85i72EuA=="], + "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], @@ -1096,10 +1151,14 @@ "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + "morgan": ["morgan@1.10.1", "", { "dependencies": { "basic-auth": "~2.0.1", "debug": "2.6.9", "depd": "~2.0.0", "on-finished": "~2.3.0", "on-headers": "~1.1.0" } }, "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -1108,10 +1167,16 @@ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "nanostores": ["nanostores@1.1.0", "", {}, "sha512-yJBmDJr18xy47dbNVlHcgdPrulSn1nhSE6Ns9vTG+Nx9VPT6iV1MD6aQFp/t52zpf82FhLLTXAXr30NuCnxvwA=="], + + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], + "node-abi": ["node-abi@3.85.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg=="], + "node-cron": ["node-cron@4.2.1", "", {}, "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg=="], "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], @@ -1134,6 +1199,8 @@ "on-headers": ["on-headers@1.1.0", "", {}, "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="], + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "one-time": ["one-time@1.0.0", "", { "dependencies": { "fn.name": "1.x.x" } }, "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g=="], "open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], @@ -1160,6 +1227,22 @@ "perfect-debounce": ["perfect-debounce@2.0.0", "", {}, "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow=="], + "pg": ["pg@8.16.3", "", { "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", "pg-protocol": "^1.10.3", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.2.7" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw=="], + + "pg-cloudflare": ["pg-cloudflare@1.2.7", "", {}, "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg=="], + + "pg-connection-string": ["pg-connection-string@2.9.1", "", {}, "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="], + + "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], + + "pg-pool": ["pg-pool@3.10.1", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg=="], + + "pg-protocol": ["pg-protocol@1.10.3", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="], + + "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + + "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], @@ -1170,8 +1253,18 @@ "postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="], + "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], + + "postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], + + "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], + + "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + "prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="], "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], @@ -1180,6 +1273,8 @@ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], + "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], @@ -1190,6 +1285,8 @@ "raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="], + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], "react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], @@ -1240,6 +1337,8 @@ "rollup": ["rollup@4.54.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.54.0", "@rollup/rollup-android-arm64": "4.54.0", "@rollup/rollup-darwin-arm64": "4.54.0", "@rollup/rollup-darwin-x64": "4.54.0", "@rollup/rollup-freebsd-arm64": "4.54.0", "@rollup/rollup-freebsd-x64": "4.54.0", "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", "@rollup/rollup-linux-arm-musleabihf": "4.54.0", "@rollup/rollup-linux-arm64-gnu": "4.54.0", "@rollup/rollup-linux-arm64-musl": "4.54.0", "@rollup/rollup-linux-loong64-gnu": "4.54.0", "@rollup/rollup-linux-ppc64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-musl": "4.54.0", "@rollup/rollup-linux-s390x-gnu": "4.54.0", "@rollup/rollup-linux-x64-gnu": "4.54.0", "@rollup/rollup-linux-x64-musl": "4.54.0", "@rollup/rollup-openharmony-arm64": "4.54.0", "@rollup/rollup-win32-arm64-msvc": "4.54.0", "@rollup/rollup-win32-ia32-msvc": "4.54.0", "@rollup/rollup-win32-x64-gnu": "4.54.0", "@rollup/rollup-win32-x64-msvc": "4.54.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw=="], + "rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], @@ -1274,6 +1373,10 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + "slugify": ["slugify@1.6.6", "", {}, "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw=="], "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], @@ -1286,6 +1389,8 @@ "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + "stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="], "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], @@ -1298,6 +1403,8 @@ "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], @@ -1310,6 +1417,10 @@ "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + "text-hex": ["text-hex@1.0.0", "", {}, "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="], "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], @@ -1334,6 +1445,8 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], "type-fest": ["type-fest@5.0.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-GeJop7+u7BYlQ6yQCAY1nBQiRSHR+6OdCEtd8Bwp9a3NK3+fWAVjOaPKJDteB9f6cIJ0wt4IfnScjLG450EpXA=="], @@ -1404,10 +1517,14 @@ "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], "wsl-utils": ["wsl-utils@0.3.0", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-3sFIGLiaDP7rTO4xh3g+b3AzhYDIUGGywE/WsmqzJWDxus5aJXVnPTNC/6L+r2WzrwXqVOdD262OaO+cEyPMSQ=="], + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], diff --git a/package.json b/package.json index f13d45de..0cb6272e 100644 --- a/package.json +++ b/package.json @@ -1,114 +1,115 @@ { - "name": "zerobyte", - "private": true, - "type": "module", - "packageManager": "bun@1.3.5", - "scripts": { - "lint": "oxlint --type-aware", - "build": "react-router build", - "dev": "bunx --bun vite", - "start": "bun ./dist/server/index.js", - "cli:dev": "bun run app/server/cli/main.ts", - "cli": "ZEROBYTE_CLI=1 bun ./dist/server/index.js", - "tsc": "react-router typegen && tsc", - "start:dev": "docker compose down && docker compose up --build zerobyte-dev", - "start:prod": "docker compose down && docker compose up --build zerobyte-prod", - "gen:api-client": "openapi-ts", - "gen:migrations": "drizzle-kit generate", - "studio": "drizzle-kit studio", - "test:server": "dotenv -e .env.test -- bun test app/server --preload ./app/test/setup.ts", - "test:client": "dotenv -e .env.test -- bun test app/client --preload ./app/test/setup-client.ts", - "test": "bun run test:server && bun run test:client" - }, - "overrides": { - "esbuild": "^0.27.2" - }, - "dependencies": { - "@dnd-kit/core": "^6.3.1", - "@dnd-kit/sortable": "^10.0.0", - "@dnd-kit/utilities": "^3.2.2", - "@hono/standard-validator": "^0.2.0", - "@hookform/resolvers": "^5.2.2", - "@inquirer/prompts": "^8.0.2", - "@radix-ui/react-alert-dialog": "^1.1.15", - "@radix-ui/react-checkbox": "^1.3.3", - "@radix-ui/react-collapsible": "^1.1.12", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-hover-card": "^1.1.15", - "@radix-ui/react-label": "^2.1.8", - "@radix-ui/react-progress": "^1.1.8", - "@radix-ui/react-scroll-area": "^1.2.10", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-separator": "^1.1.8", - "@radix-ui/react-slot": "^1.2.4", - "@radix-ui/react-switch": "^1.2.6", - "@radix-ui/react-tabs": "^1.1.13", - "@radix-ui/react-tooltip": "^1.2.8", - "@react-router/node": "^7.10.0", - "@react-router/serve": "^7.10.0", - "@scalar/hono-api-reference": "^0.9.25", - "@tanstack/react-query": "^5.90.11", - "arktype": "^2.1.28", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "commander": "^14.0.2", - "cron-parser": "^5.4.0", - "date-fns": "^4.1.0", - "dither-plugin": "^1.1.1", - "dotenv": "^17.2.3", - "drizzle-orm": "^0.44.7", - "es-toolkit": "^1.42.0", - "hono": "4.10.5", - "hono-openapi": "^1.1.1", - "hono-rate-limiter": "^0.5.0", - "http-errors-enhanced": "^4.0.2", - "isbot": "^5.1.32", - "lucide-react": "^0.555.0", - "next-themes": "^0.4.6", - "node-cron": "^4.2.1", - "react": "^19.2.1", - "react-dom": "^19.2.1", - "react-hook-form": "^7.68.0", - "react-markdown": "^10.1.0", - "react-router": "^7.10.0", - "react-router-hono-server": "^2.22.0", - "recharts": "3.5.1", - "remark-gfm": "^4.0.1", - "semver": "^7.7.3", - "slugify": "^1.6.6", - "sonner": "^2.0.7", - "tailwind-merge": "^3.4.0", - "tiny-typed-emitter": "^2.1.0", - "winston": "^3.18.3", - "yaml": "^2.8.2" - }, - "devDependencies": { - "@faker-js/faker": "^10.1.0", - "@happy-dom/global-registrator": "^20.0.11", - "@hey-api/openapi-ts": "^0.88.0", - "@react-router/dev": "^7.10.0", - "@tailwindcss/typography": "^0.5.19", - "@tailwindcss/vite": "^4.1.17", - "@tanstack/react-query-devtools": "^5.91.1", - "@testing-library/dom": "^10.4.1", - "@testing-library/react": "^16.3.1", - "@types/bun": "^1.3.4", - "@types/node": "^25.0.3", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", - "@types/semver": "^7.7.1", - "dotenv-cli": "^11.0.0", - "drizzle-kit": "^0.31.7", - "lightningcss": "^1.30.2", - "oxfmt": "^0.22.0", - "oxlint": "^1.36.0", - "oxlint-tsgolint": "^0.10.1", - "tailwindcss": "^4.1.17", - "tinyglobby": "^0.2.15", - "tw-animate-css": "^1.4.0", - "typescript": "^5.9.3", - "vite": "^7.2.6", - "vite-bundle-analyzer": "^1.2.3", - "vite-tsconfig-paths": "^6.0.3" - } + "name": "zerobyte", + "private": true, + "type": "module", + "packageManager": "bun@1.3.5", + "scripts": { + "lint": "oxlint --type-aware", + "build": "react-router build", + "dev": "bunx --bun vite", + "start": "bun ./dist/server/index.js", + "cli:dev": "bun run app/server/cli/main.ts", + "cli": "ZEROBYTE_CLI=1 bun ./dist/server/index.js", + "tsc": "react-router typegen && tsc", + "start:dev": "docker compose down && docker compose up --build zerobyte-dev", + "start:prod": "docker compose down && docker compose up --build zerobyte-prod", + "gen:api-client": "openapi-ts", + "gen:migrations": "drizzle-kit generate", + "studio": "drizzle-kit studio", + "test:server": "dotenv -e .env.test -- bun test app/server --preload ./app/test/setup.ts", + "test:client": "dotenv -e .env.test -- bun test app/client --preload ./app/test/setup-client.ts", + "test": "bun run test:server && bun run test:client" + }, + "overrides": { + "esbuild": "^0.27.2" + }, + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@hono/standard-validator": "^0.2.0", + "@hookform/resolvers": "^5.2.2", + "@inquirer/prompts": "^8.0.2", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-hover-card": "^1.1.15", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-progress": "^1.1.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", + "@react-router/node": "^7.10.0", + "@react-router/serve": "^7.10.0", + "@scalar/hono-api-reference": "^0.9.25", + "@tanstack/react-query": "^5.90.11", + "arktype": "^2.1.28", + "better-auth": "^1.4.10", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "commander": "^14.0.2", + "cron-parser": "^5.4.0", + "date-fns": "^4.1.0", + "dither-plugin": "^1.1.1", + "dotenv": "^17.2.3", + "drizzle-orm": "^0.44.7", + "es-toolkit": "^1.42.0", + "hono": "4.10.5", + "hono-openapi": "^1.1.1", + "hono-rate-limiter": "^0.5.0", + "http-errors-enhanced": "^4.0.2", + "isbot": "^5.1.32", + "lucide-react": "^0.555.0", + "next-themes": "^0.4.6", + "node-cron": "^4.2.1", + "react": "^19.2.1", + "react-dom": "^19.2.1", + "react-hook-form": "^7.68.0", + "react-markdown": "^10.1.0", + "react-router": "^7.10.0", + "react-router-hono-server": "^2.22.0", + "recharts": "3.5.1", + "remark-gfm": "^4.0.1", + "semver": "^7.7.3", + "slugify": "^1.6.6", + "sonner": "^2.0.7", + "tailwind-merge": "^3.4.0", + "tiny-typed-emitter": "^2.1.0", + "winston": "^3.18.3", + "yaml": "^2.8.2" + }, + "devDependencies": { + "@faker-js/faker": "^10.1.0", + "@happy-dom/global-registrator": "^20.0.11", + "@hey-api/openapi-ts": "^0.88.0", + "@react-router/dev": "^7.10.0", + "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.1.17", + "@tanstack/react-query-devtools": "^5.91.1", + "@testing-library/dom": "^10.4.1", + "@testing-library/react": "^16.3.1", + "@types/bun": "^1.3.4", + "@types/node": "^25.0.3", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@types/semver": "^7.7.1", + "dotenv-cli": "^11.0.0", + "drizzle-kit": "^0.31.7", + "lightningcss": "^1.30.2", + "oxfmt": "^0.22.0", + "oxlint": "^1.36.0", + "oxlint-tsgolint": "^0.10.1", + "tailwindcss": "^4.1.17", + "tinyglobby": "^0.2.15", + "tw-animate-css": "^1.4.0", + "typescript": "^5.9.3", + "vite": "^7.2.6", + "vite-bundle-analyzer": "^1.2.3", + "vite-tsconfig-paths": "^6.0.3" + } }