diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 3989553bb9..f8ad861274 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -133,7 +133,7 @@ Class | Method | HTTP request | Description *AuthenticationApi* | [**unlockAuthSession**](doc//AuthenticationApi.md#unlockauthsession) | **POST** /auth/session/unlock | Unlock auth session *AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken | Validate access token *AuthenticationAdminApi* | [**unlinkAllOAuthAccountsAdmin**](doc//AuthenticationAdminApi.md#unlinkalloauthaccountsadmin) | **POST** /admin/auth/unlink-all | Unlink all OAuth accounts -*DatabaseBackupsAdminApi* | [**deleteDatabaseBackup**](doc//DatabaseBackupsAdminApi.md#deletedatabasebackup) | **DELETE** /admin/database-backups/{filename} | Delete database backup +*DatabaseBackupsAdminApi* | [**deleteDatabaseBackup**](doc//DatabaseBackupsAdminApi.md#deletedatabasebackup) | **DELETE** /admin/database-backups | Delete database backup *DatabaseBackupsAdminApi* | [**downloadDatabaseBackup**](doc//DatabaseBackupsAdminApi.md#downloaddatabasebackup) | **GET** /admin/database-backups/{filename} | Download database backup *DatabaseBackupsAdminApi* | [**listDatabaseBackups**](doc//DatabaseBackupsAdminApi.md#listdatabasebackups) | **GET** /admin/database-backups | List database backups *DatabaseBackupsAdminApi* | [**startDatabaseRestoreFlow**](doc//DatabaseBackupsAdminApi.md#startdatabaserestoreflow) | **POST** /admin/database-backups/start-restore | Start database backup restore flow @@ -394,6 +394,8 @@ Class | Method | HTTP request | Description - [CreateLibraryDto](doc//CreateLibraryDto.md) - [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md) - [DatabaseBackupConfig](doc//DatabaseBackupConfig.md) + - [DatabaseBackupDeleteDto](doc//DatabaseBackupDeleteDto.md) + - [DatabaseBackupListResponseDto](doc//DatabaseBackupListResponseDto.md) - [DownloadArchiveInfo](doc//DownloadArchiveInfo.md) - [DownloadInfoDto](doc//DownloadInfoDto.md) - [DownloadResponse](doc//DownloadResponse.md) @@ -425,7 +427,6 @@ Class | Method | HTTP request | Description - [MaintenanceAuthDto](doc//MaintenanceAuthDto.md) - [MaintenanceDetectInstallResponseDto](doc//MaintenanceDetectInstallResponseDto.md) - [MaintenanceDetectInstallStorageFolderDto](doc//MaintenanceDetectInstallStorageFolderDto.md) - - [MaintenanceListBackupsResponseDto](doc//MaintenanceListBackupsResponseDto.md) - [MaintenanceLoginDto](doc//MaintenanceLoginDto.md) - [MaintenanceStatusResponseDto](doc//MaintenanceStatusResponseDto.md) - [ManualJobName](doc//ManualJobName.md) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 4b89f85c86..2d493239fd 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -140,6 +140,8 @@ part 'model/create_album_dto.dart'; part 'model/create_library_dto.dart'; part 'model/create_profile_image_response_dto.dart'; part 'model/database_backup_config.dart'; +part 'model/database_backup_delete_dto.dart'; +part 'model/database_backup_list_response_dto.dart'; part 'model/download_archive_info.dart'; part 'model/download_info_dto.dart'; part 'model/download_response.dart'; @@ -171,7 +173,6 @@ part 'model/maintenance_action.dart'; part 'model/maintenance_auth_dto.dart'; part 'model/maintenance_detect_install_response_dto.dart'; part 'model/maintenance_detect_install_storage_folder_dto.dart'; -part 'model/maintenance_list_backups_response_dto.dart'; part 'model/maintenance_login_dto.dart'; part 'model/maintenance_status_response_dto.dart'; part 'model/manual_job_name.dart'; diff --git a/mobile/openapi/lib/api/database_backups_admin_api.dart b/mobile/openapi/lib/api/database_backups_admin_api.dart index 54ac5ac0ae..fbd485f86f 100644 --- a/mobile/openapi/lib/api/database_backups_admin_api.dart +++ b/mobile/openapi/lib/api/database_backups_admin_api.dart @@ -24,20 +24,19 @@ class DatabaseBackupsAdminApi { /// /// Parameters: /// - /// * [String] filename (required): - Future deleteDatabaseBackupWithHttpInfo(String filename,) async { + /// * [DatabaseBackupDeleteDto] databaseBackupDeleteDto (required): + Future deleteDatabaseBackupWithHttpInfo(DatabaseBackupDeleteDto databaseBackupDeleteDto,) async { // ignore: prefer_const_declarations - final apiPath = r'/admin/database-backups/{filename}' - .replaceAll('{filename}', filename); + final apiPath = r'/admin/database-backups'; // ignore: prefer_final_locals - Object? postBody; + Object? postBody = databaseBackupDeleteDto; final queryParams = []; final headerParams = {}; final formParams = {}; - const contentTypes = []; + const contentTypes = ['application/json']; return apiClient.invokeAPI( @@ -57,9 +56,9 @@ class DatabaseBackupsAdminApi { /// /// Parameters: /// - /// * [String] filename (required): - Future deleteDatabaseBackup(String filename,) async { - final response = await deleteDatabaseBackupWithHttpInfo(filename,); + /// * [DatabaseBackupDeleteDto] databaseBackupDeleteDto (required): + Future deleteDatabaseBackup(DatabaseBackupDeleteDto databaseBackupDeleteDto,) async { + final response = await deleteDatabaseBackupWithHttpInfo(databaseBackupDeleteDto,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -155,7 +154,7 @@ class DatabaseBackupsAdminApi { /// List database backups /// /// Get the list of the successful and failed backups - Future listDatabaseBackups() async { + Future listDatabaseBackups() async { final response = await listDatabaseBackupsWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); @@ -164,7 +163,7 @@ class DatabaseBackupsAdminApi { // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" // FormatException when trying to decode an empty string. if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MaintenanceListBackupsResponseDto',) as MaintenanceListBackupsResponseDto; + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'DatabaseBackupListResponseDto',) as DatabaseBackupListResponseDto; } return null; diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 5a38a80c91..8a803496df 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -326,6 +326,10 @@ class ApiClient { return CreateProfileImageResponseDto.fromJson(value); case 'DatabaseBackupConfig': return DatabaseBackupConfig.fromJson(value); + case 'DatabaseBackupDeleteDto': + return DatabaseBackupDeleteDto.fromJson(value); + case 'DatabaseBackupListResponseDto': + return DatabaseBackupListResponseDto.fromJson(value); case 'DownloadArchiveInfo': return DownloadArchiveInfo.fromJson(value); case 'DownloadInfoDto': @@ -388,8 +392,6 @@ class ApiClient { return MaintenanceDetectInstallResponseDto.fromJson(value); case 'MaintenanceDetectInstallStorageFolderDto': return MaintenanceDetectInstallStorageFolderDto.fromJson(value); - case 'MaintenanceListBackupsResponseDto': - return MaintenanceListBackupsResponseDto.fromJson(value); case 'MaintenanceLoginDto': return MaintenanceLoginDto.fromJson(value); case 'MaintenanceStatusResponseDto': diff --git a/mobile/openapi/lib/model/maintenance_list_backups_response_dto.dart b/mobile/openapi/lib/model/database_backup_delete_dto.dart similarity index 57% rename from mobile/openapi/lib/model/maintenance_list_backups_response_dto.dart rename to mobile/openapi/lib/model/database_backup_delete_dto.dart index 0d5b6ba409..8bc33a81dc 100644 --- a/mobile/openapi/lib/model/maintenance_list_backups_response_dto.dart +++ b/mobile/openapi/lib/model/database_backup_delete_dto.dart @@ -10,16 +10,16 @@ part of openapi.api; -class MaintenanceListBackupsResponseDto { - /// Returns a new [MaintenanceListBackupsResponseDto] instance. - MaintenanceListBackupsResponseDto({ +class DatabaseBackupDeleteDto { + /// Returns a new [DatabaseBackupDeleteDto] instance. + DatabaseBackupDeleteDto({ this.backups = const [], }); List backups; @override - bool operator ==(Object other) => identical(this, other) || other is MaintenanceListBackupsResponseDto && + bool operator ==(Object other) => identical(this, other) || other is DatabaseBackupDeleteDto && _deepEquality.equals(other.backups, backups); @override @@ -28,7 +28,7 @@ class MaintenanceListBackupsResponseDto { (backups.hashCode); @override - String toString() => 'MaintenanceListBackupsResponseDto[backups=$backups]'; + String toString() => 'DatabaseBackupDeleteDto[backups=$backups]'; Map toJson() { final json = {}; @@ -36,15 +36,15 @@ class MaintenanceListBackupsResponseDto { return json; } - /// Returns a new [MaintenanceListBackupsResponseDto] instance and imports its values from + /// Returns a new [DatabaseBackupDeleteDto] instance and imports its values from /// [value] if it's a [Map], null otherwise. // ignore: prefer_constructors_over_static_methods - static MaintenanceListBackupsResponseDto? fromJson(dynamic value) { - upgradeDto(value, "MaintenanceListBackupsResponseDto"); + static DatabaseBackupDeleteDto? fromJson(dynamic value) { + upgradeDto(value, "DatabaseBackupDeleteDto"); if (value is Map) { final json = value.cast(); - return MaintenanceListBackupsResponseDto( + return DatabaseBackupDeleteDto( backups: json[r'backups'] is Iterable ? (json[r'backups'] as Iterable).cast().toList(growable: false) : const [], @@ -53,11 +53,11 @@ class MaintenanceListBackupsResponseDto { return null; } - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; if (json is List && json.isNotEmpty) { for (final row in json) { - final value = MaintenanceListBackupsResponseDto.fromJson(row); + final value = DatabaseBackupDeleteDto.fromJson(row); if (value != null) { result.add(value); } @@ -66,12 +66,12 @@ class MaintenanceListBackupsResponseDto { return result.toList(growable: growable); } - static Map mapFromJson(dynamic json) { - final map = {}; + static Map mapFromJson(dynamic json) { + final map = {}; if (json is Map && json.isNotEmpty) { json = json.cast(); // ignore: parameter_assignments for (final entry in json.entries) { - final value = MaintenanceListBackupsResponseDto.fromJson(entry.value); + final value = DatabaseBackupDeleteDto.fromJson(entry.value); if (value != null) { map[entry.key] = value; } @@ -80,14 +80,14 @@ class MaintenanceListBackupsResponseDto { return map; } - // maps a json object with a list of MaintenanceListBackupsResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; + // maps a json object with a list of DatabaseBackupDeleteDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; if (json is Map && json.isNotEmpty) { // ignore: parameter_assignments json = json.cast(); for (final entry in json.entries) { - map[entry.key] = MaintenanceListBackupsResponseDto.listFromJson(entry.value, growable: growable,); + map[entry.key] = DatabaseBackupDeleteDto.listFromJson(entry.value, growable: growable,); } } return map; diff --git a/mobile/openapi/lib/model/database_backup_list_response_dto.dart b/mobile/openapi/lib/model/database_backup_list_response_dto.dart new file mode 100644 index 0000000000..3cb8744f14 --- /dev/null +++ b/mobile/openapi/lib/model/database_backup_list_response_dto.dart @@ -0,0 +1,101 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class DatabaseBackupListResponseDto { + /// Returns a new [DatabaseBackupListResponseDto] instance. + DatabaseBackupListResponseDto({ + this.backups = const [], + }); + + List backups; + + @override + bool operator ==(Object other) => identical(this, other) || other is DatabaseBackupListResponseDto && + _deepEquality.equals(other.backups, backups); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (backups.hashCode); + + @override + String toString() => 'DatabaseBackupListResponseDto[backups=$backups]'; + + Map toJson() { + final json = {}; + json[r'backups'] = this.backups; + return json; + } + + /// Returns a new [DatabaseBackupListResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static DatabaseBackupListResponseDto? fromJson(dynamic value) { + upgradeDto(value, "DatabaseBackupListResponseDto"); + if (value is Map) { + final json = value.cast(); + + return DatabaseBackupListResponseDto( + backups: json[r'backups'] is Iterable + ? (json[r'backups'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = DatabaseBackupListResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = DatabaseBackupListResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of DatabaseBackupListResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = DatabaseBackupListResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'backups', + }; +} + diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index f185de44d6..3adccdee24 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -323,6 +323,54 @@ } }, "/admin/database-backups": { + "delete": { + "description": "Delete a backup by its filename", + "operationId": "deleteDatabaseBackup", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatabaseBackupDeleteDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "summary": "Delete database backup", + "tags": [ + "Database Backups (admin)" + ], + "x-immich-admin-only": true, + "x-immich-history": [ + { + "version": "v2.4.0", + "state": "Added" + }, + { + "version": "v2.4.0", + "state": "Alpha" + } + ], + "x-immich-permission": "backup.delete", + "x-immich-state": "Alpha" + }, "get": { "description": "Get the list of the successful and failed backups", "operationId": "listDatabaseBackups", @@ -332,7 +380,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/MaintenanceListBackupsResponseDto" + "$ref": "#/components/schemas/DatabaseBackupListResponseDto" } } }, @@ -405,7 +453,7 @@ "content": { "multipart/form-data": { "schema": { - "$ref": "#/components/schemas/MaintenanceUploadBackupDto" + "$ref": "#/components/schemas/DatabaseBackupUploadDto" } } }, @@ -448,54 +496,6 @@ } }, "/admin/database-backups/{filename}": { - "delete": { - "description": "Delete a backup by its filename", - "operationId": "deleteDatabaseBackup", - "parameters": [ - { - "name": "filename", - "required": true, - "in": "path", - "schema": { - "format": "string", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ], - "summary": "Delete database backup", - "tags": [ - "Database Backups (admin)" - ], - "x-immich-admin-only": true, - "x-immich-history": [ - { - "version": "v2.4.0", - "state": "Added" - }, - { - "version": "v2.4.0", - "state": "Alpha" - } - ], - "x-immich-permission": "backup.delete", - "x-immich-state": "Alpha" - }, "get": { "description": "Downloads the database backup file", "operationId": "downloadDatabaseBackup", @@ -16549,6 +16549,43 @@ ], "type": "object" }, + "DatabaseBackupDeleteDto": { + "properties": { + "backups": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "backups" + ], + "type": "object" + }, + "DatabaseBackupListResponseDto": { + "properties": { + "backups": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "backups" + ], + "type": "object" + }, + "DatabaseBackupUploadDto": { + "properties": { + "file": { + "format": "binary", + "type": "string" + } + }, + "type": "object" + }, "DownloadArchiveInfo": { "properties": { "assetIds": { @@ -17272,20 +17309,6 @@ ], "type": "object" }, - "MaintenanceListBackupsResponseDto": { - "properties": { - "backups": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "backups" - ], - "type": "object" - }, "MaintenanceLoginDto": { "properties": { "token": { @@ -17322,15 +17345,6 @@ ], "type": "object" }, - "MaintenanceUploadBackupDto": { - "properties": { - "file": { - "format": "binary", - "type": "string" - } - }, - "type": "object" - }, "ManualJobName": { "enum": [ "person-cleanup", diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 9512b64d2c..5f259472a0 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -40,10 +40,13 @@ export type ActivityStatisticsResponseDto = { comments: number; likes: number; }; -export type MaintenanceListBackupsResponseDto = { +export type DatabaseBackupDeleteDto = { backups: string[]; }; -export type MaintenanceUploadBackupDto = { +export type DatabaseBackupListResponseDto = { + backups: string[]; +}; +export type DatabaseBackupUploadDto = { file?: Blob; }; export type SetMaintenanceModeDto = { @@ -1873,13 +1876,25 @@ export function unlinkAllOAuthAccountsAdmin(opts?: Oazapfts.RequestOpts) { method: "POST" })); } +/** + * Delete database backup + */ +export function deleteDatabaseBackup({ databaseBackupDeleteDto }: { + databaseBackupDeleteDto: DatabaseBackupDeleteDto; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchText("/admin/database-backups", oazapfts.json({ + ...opts, + method: "DELETE", + body: databaseBackupDeleteDto + }))); +} /** * List database backups */ export function listDatabaseBackups(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; - data: MaintenanceListBackupsResponseDto; + data: DatabaseBackupListResponseDto; }>("/admin/database-backups", { ...opts })); @@ -1896,26 +1911,15 @@ export function startDatabaseRestoreFlow(opts?: Oazapfts.RequestOpts) { /** * Upload database backup */ -export function uploadDatabaseBackup({ maintenanceUploadBackupDto }: { - maintenanceUploadBackupDto: MaintenanceUploadBackupDto; +export function uploadDatabaseBackup({ databaseBackupUploadDto }: { + databaseBackupUploadDto: DatabaseBackupUploadDto; }, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchText("/admin/database-backups/upload", oazapfts.multipart({ ...opts, method: "POST", - body: maintenanceUploadBackupDto + body: databaseBackupUploadDto }))); } -/** - * Delete database backup - */ -export function deleteDatabaseBackup({ filename }: { - filename: string; -}, opts?: Oazapfts.RequestOpts) { - return oazapfts.ok(oazapfts.fetchText(`/admin/database-backups/${encodeURIComponent(filename)}`, { - ...opts, - method: "DELETE" - })); -} /** * Download database backup */ diff --git a/server/src/controllers/database-backup.controller.ts b/server/src/controllers/database-backup.controller.ts index dd8a69356b..1d4f49db9f 100644 --- a/server/src/controllers/database-backup.controller.ts +++ b/server/src/controllers/database-backup.controller.ts @@ -1,9 +1,13 @@ -import { Controller, Delete, Get, Next, Param, Post, Res, UploadedFile, UseInterceptors } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Next, Param, Post, Res, UploadedFile, UseInterceptors } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger'; import { NextFunction, Response } from 'express'; import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { MaintenanceListBackupsResponseDto, MaintenanceUploadBackupDto } from 'src/dtos/maintenance.dto'; +import { + DatabaseBackupDeleteDto, + DatabaseBackupListResponseDto, + DatabaseBackupUploadDto, +} from 'src/dtos/database-backup.dto'; import { ApiTag, ImmichCookie, Permission } from 'src/enum'; import { Authenticated, FileResponse, GetLoginDetails } from 'src/middleware/auth.guard'; import { LoggingRepository } from 'src/repositories/logging.repository'; @@ -30,7 +34,7 @@ export class DatabaseBackupController { history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'), }) @Authenticated({ permission: Permission.Maintenance, admin: true }) - listDatabaseBackups(): Promise { + listDatabaseBackups(): Promise { return this.service.listBackups(); } @@ -50,15 +54,15 @@ export class DatabaseBackupController { await sendFile(res, next, () => this.service.downloadBackup(filename), this.logger); } - @Delete(':filename') + @Delete() @Endpoint({ summary: 'Delete database backup', description: 'Delete a backup by its filename', history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'), }) @Authenticated({ permission: Permission.BackupDelete, admin: true }) - async deleteDatabaseBackup(@Param() { filename }: FilenameParamDto): Promise { - return this.service.deleteBackup(filename); + async deleteDatabaseBackup(@Body() dto: DatabaseBackupDeleteDto): Promise { + return this.service.deleteBackup(dto.backups); } @Post('start-restore') @@ -81,7 +85,7 @@ export class DatabaseBackupController { @Post('upload') @Authenticated({ permission: Permission.BackupUpload, admin: true }) @ApiConsumes('multipart/form-data') - @ApiBody({ description: 'Backup Upload', type: MaintenanceUploadBackupDto }) + @ApiBody({ description: 'Backup Upload', type: DatabaseBackupUploadDto }) @Endpoint({ summary: 'Upload database backup', description: 'Uploads .sql/.sql.gz file to restore backup from', diff --git a/server/src/dtos/database-backup.dto.ts b/server/src/dtos/database-backup.dto.ts new file mode 100644 index 0000000000..ab6845eed2 --- /dev/null +++ b/server/src/dtos/database-backup.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class DatabaseBackupListResponseDto { + backups!: string[]; +} + +export class DatabaseBackupUploadDto { + @ApiProperty({ type: 'string', format: 'binary', required: false }) + file?: any; +} + +export class DatabaseBackupDeleteDto { + @IsString({ each: true }) + backups!: string[]; +} diff --git a/server/src/dtos/maintenance.dto.ts b/server/src/dtos/maintenance.dto.ts index 4e76b1fbb1..8eff1decd8 100644 --- a/server/src/dtos/maintenance.dto.ts +++ b/server/src/dtos/maintenance.dto.ts @@ -1,4 +1,3 @@ -import { ApiProperty } from '@nestjs/swagger'; import { MaintenanceAction, StorageFolder } from 'src/enum'; import { ValidateEnum, ValidateString } from 'src/validation'; @@ -41,12 +40,3 @@ export class MaintenanceDetectInstallStorageFolderDto { export class MaintenanceDetectInstallResponseDto { storage!: MaintenanceDetectInstallStorageFolderDto[]; } - -export class MaintenanceListBackupsResponseDto { - backups!: string[]; -} - -export class MaintenanceUploadBackupDto { - @ApiProperty({ type: 'string', format: 'binary', required: false }) - file?: any; -} diff --git a/server/src/maintenance/maintenance-worker.controller.ts b/server/src/maintenance/maintenance-worker.controller.ts index f9c193dad2..82949fe665 100644 --- a/server/src/maintenance/maintenance-worker.controller.ts +++ b/server/src/maintenance/maintenance-worker.controller.ts @@ -16,7 +16,6 @@ import { NextFunction, Request, Response } from 'express'; import { MaintenanceAuthDto, MaintenanceDetectInstallResponseDto, - MaintenanceListBackupsResponseDto, MaintenanceLoginDto, MaintenanceStatusResponseDto, SetMaintenanceModeDto, @@ -34,6 +33,7 @@ import { FilenameParamDto } from 'src/validation'; import type { DatabaseBackupController as _DatabaseBackupController } from 'src/controllers/database-backup.controller'; import type { ServerController as _ServerController } from 'src/controllers/server.controller'; +import { DatabaseBackupListResponseDto } from 'src/dtos/database-backup.dto'; @Controller() export class MaintenanceWorkerController { @@ -55,7 +55,7 @@ export class MaintenanceWorkerController { */ @Get('admin/database-backups') @MaintenanceRoute() - listDatabaseBackups(): Promise { + listDatabaseBackups(): Promise { return this.service.listBackups(); } diff --git a/server/src/services/database-backup.service.ts b/server/src/services/database-backup.service.ts index 0e1bda9249..6333dbba12 100644 --- a/server/src/services/database-backup.service.ts +++ b/server/src/services/database-backup.service.ts @@ -15,8 +15,8 @@ export class DatabaseBackupService extends BaseService { return { backups: await listBackups(this.backupRepos) }; } - async deleteBackup(filename: string): Promise { - return deleteBackup(this.backupRepos, basename(filename)); + async deleteBackup(files: string[]): Promise { + await Promise.all(files.map((filename) => deleteBackup(this.backupRepos, basename(filename)))); } async uploadBackup(file: Express.Multer.File): Promise { diff --git a/web/src/lib/components/maintenance/MaintenanceBackupsList.svelte b/web/src/lib/components/maintenance/MaintenanceBackupsList.svelte index 0de63ffa61..88ccf0d844 100644 --- a/web/src/lib/components/maintenance/MaintenanceBackupsList.svelte +++ b/web/src/lib/components/maintenance/MaintenanceBackupsList.svelte @@ -8,7 +8,7 @@ listDatabaseBackups, MaintenanceAction, setMaintenanceMode, - type MaintenanceUploadBackupDto, + type DatabaseBackupUploadDto, } from '@immich/sdk'; import { Button, @@ -93,7 +93,9 @@ deleting.add(filename); await deleteDatabaseBackup({ - filename, + databaseBackupDeleteDto: { + backups: [filename], + }, }); backups = backups.filter((backup) => backup.filename !== filename); @@ -141,7 +143,7 @@ const formData = new FormData(); formData.append('file', file); - await uploadRequest({ + await uploadRequest({ url: getBaseUrl() + '/admin/database-backups/upload', data: formData, onUploadProgress(event) {