refactor: instead of param, allow bulk backup deletion

This commit is contained in:
izzy
2025-12-03 11:55:15 +00:00
parent a63b418507
commit 207a8bc55a
14 changed files with 283 additions and 149 deletions

View File

@@ -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)

View File

@@ -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';

View File

@@ -24,20 +24,19 @@ class DatabaseBackupsAdminApi {
///
/// Parameters:
///
/// * [String] filename (required):
Future<Response> deleteDatabaseBackupWithHttpInfo(String filename,) async {
/// * [DatabaseBackupDeleteDto] databaseBackupDeleteDto (required):
Future<Response> 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 = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
@@ -57,9 +56,9 @@ class DatabaseBackupsAdminApi {
///
/// Parameters:
///
/// * [String] filename (required):
Future<void> deleteDatabaseBackup(String filename,) async {
final response = await deleteDatabaseBackupWithHttpInfo(filename,);
/// * [DatabaseBackupDeleteDto] databaseBackupDeleteDto (required):
Future<void> 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<MaintenanceListBackupsResponseDto?> listDatabaseBackups() async {
Future<DatabaseBackupListResponseDto?> 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;

View File

@@ -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':

View File

@@ -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<String> 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<String, dynamic> toJson() {
final json = <String, dynamic>{};
@@ -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<String, dynamic>();
return MaintenanceListBackupsResponseDto(
return DatabaseBackupDeleteDto(
backups: json[r'backups'] is Iterable
? (json[r'backups'] as Iterable).cast<String>().toList(growable: false)
: const [],
@@ -53,11 +53,11 @@ class MaintenanceListBackupsResponseDto {
return null;
}
static List<MaintenanceListBackupsResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <MaintenanceListBackupsResponseDto>[];
static List<DatabaseBackupDeleteDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <DatabaseBackupDeleteDto>[];
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<String, MaintenanceListBackupsResponseDto> mapFromJson(dynamic json) {
final map = <String, MaintenanceListBackupsResponseDto>{};
static Map<String, DatabaseBackupDeleteDto> mapFromJson(dynamic json) {
final map = <String, DatabaseBackupDeleteDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // 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<String, List<MaintenanceListBackupsResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<MaintenanceListBackupsResponseDto>>{};
// maps a json object with a list of DatabaseBackupDeleteDto-objects as value to a dart map
static Map<String, List<DatabaseBackupDeleteDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<DatabaseBackupDeleteDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
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;

View File

@@ -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<String> 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<String, dynamic> toJson() {
final json = <String, dynamic>{};
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<String, dynamic>();
return DatabaseBackupListResponseDto(
backups: json[r'backups'] is Iterable
? (json[r'backups'] as Iterable).cast<String>().toList(growable: false)
: const [],
);
}
return null;
}
static List<DatabaseBackupListResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <DatabaseBackupListResponseDto>[];
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<String, DatabaseBackupListResponseDto> mapFromJson(dynamic json) {
final map = <String, DatabaseBackupListResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // 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<String, List<DatabaseBackupListResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<DatabaseBackupListResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
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 = <String>{
'backups',
};
}

View File

@@ -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",

View File

@@ -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
*/

View File

@@ -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<MaintenanceListBackupsResponseDto> {
listDatabaseBackups(): Promise<DatabaseBackupListResponseDto> {
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<void> {
return this.service.deleteBackup(filename);
async deleteDatabaseBackup(@Body() dto: DatabaseBackupDeleteDto): Promise<void> {
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',

View File

@@ -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[];
}

View File

@@ -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;
}

View File

@@ -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<MaintenanceListBackupsResponseDto> {
listDatabaseBackups(): Promise<DatabaseBackupListResponseDto> {
return this.service.listBackups();
}

View File

@@ -15,8 +15,8 @@ export class DatabaseBackupService extends BaseService {
return { backups: await listBackups(this.backupRepos) };
}
async deleteBackup(filename: string): Promise<void> {
return deleteBackup(this.backupRepos, basename(filename));
async deleteBackup(files: string[]): Promise<void> {
await Promise.all(files.map((filename) => deleteBackup(this.backupRepos, basename(filename))));
}
async uploadBackup(file: Express.Multer.File): Promise<void> {

View File

@@ -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<MaintenanceUploadBackupDto>({
await uploadRequest<DatabaseBackupUploadDto>({
url: getBaseUrl() + '/admin/database-backups/upload',
data: formData,
onUploadProgress(event) {