diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 9f4958e761..087957d343 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -372,6 +372,103 @@ "x-immich-state": "Alpha" } }, + "/admin/maintenance/admin/maintenance/backups/list": { + "get": { + "description": "Get the list of the successful and failed backups", + "operationId": "listBackups", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MaintenanceListBackupsResponseDto" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "summary": "List backups", + "tags": [ + "Maintenance (admin)" + ], + "x-immich-admin-only": true, + "x-immich-history": [ + { + "version": "v9.9.9", + "state": "Added" + }, + { + "version": "v9.9.9", + "state": "Alpha" + } + ], + "x-immich-permission": "maintenance", + "x-immich-state": "Alpha" + } + }, + "/admin/maintenance/admin/maintenance/backups/{filename}": { + "delete": { + "description": "Delete a backup by its filename", + "operationId": "deleteBackup", + "parameters": [ + { + "name": "filename", + "required": true, + "in": "path", + "schema": { + "format": "string", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "summary": "Delete backup", + "tags": [ + "Maintenance (admin)" + ], + "x-immich-admin-only": true, + "x-immich-history": [ + { + "version": "v9.9.9", + "state": "Added" + }, + { + "version": "v9.9.9", + "state": "Alpha" + } + ], + "x-immich-permission": "maintenance", + "x-immich-state": "Alpha" + } + }, "/admin/maintenance/admin/maintenance/status": { "get": { "description": "Fetch information about the currently running maintenance action.", @@ -16576,6 +16673,27 @@ ], "type": "object" }, + "MaintenanceListBackupsResponseDto": { + "properties": { + "backups": { + "items": { + "type": "string" + }, + "type": "array" + }, + "failedBackups": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "backups", + "failedBackups" + ], + "type": "object" + }, "MaintenanceLoginDto": { "properties": { "token": { diff --git a/server/src/controllers/maintenance.controller.ts b/server/src/controllers/maintenance.controller.ts index ac9ecc664d..c4b1724529 100644 --- a/server/src/controllers/maintenance.controller.ts +++ b/server/src/controllers/maintenance.controller.ts @@ -1,10 +1,11 @@ -import { BadRequestException, Body, Controller, Get, Post, Res } from '@nestjs/common'; +import { BadRequestException, Body, Controller, Delete, Get, Param, Post, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Response } from 'express'; import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { MaintenanceAuthDto, + MaintenanceListBackupsResponseDto, MaintenanceLoginDto, MaintenanceStatusResponseDto, SetMaintenanceModeDto, @@ -14,6 +15,7 @@ import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard' import { LoginDetails } from 'src/services/auth.service'; import { MaintenanceService } from 'src/services/maintenance.service'; import { respondWithCookie } from 'src/utils/response'; +import { FilenameParamDto } from 'src/validation'; @ApiTags(ApiTag.Maintenance) @Controller('admin/maintenance') @@ -63,4 +65,26 @@ export class MaintenanceController { }); } } + + @Get('admin/maintenance/backups/list') + @Endpoint({ + summary: 'List backups', + description: 'Get the list of the successful and failed backups', + history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'), + }) + @Authenticated({ permission: Permission.Maintenance, admin: true }) + listBackups(): Promise { + return this.service.listBackups(); + } + + @Delete('admin/maintenance/backups/:filename') + @Endpoint({ + summary: 'Delete backup', + description: 'Delete a backup by its filename', + history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'), + }) + @Authenticated({ permission: Permission.Maintenance, admin: true }) + async deleteBackup(@Param() { filename }: FilenameParamDto): Promise { + return this.service.deleteBackup(filename); + } } diff --git a/server/src/dtos/maintenance.dto.ts b/server/src/dtos/maintenance.dto.ts index d6e5e552e5..64bce1d438 100644 --- a/server/src/dtos/maintenance.dto.ts +++ b/server/src/dtos/maintenance.dto.ts @@ -25,3 +25,8 @@ export class MaintenanceStatusResponseDto { task?: string; error?: string; } + +export class MaintenanceListBackupsResponseDto { + backups!: string[]; + failedBackups!: string[]; +} diff --git a/server/src/maintenance/maintenance-worker.controller.ts b/server/src/maintenance/maintenance-worker.controller.ts index ad8b4aae1e..f14cc2b3ee 100644 --- a/server/src/maintenance/maintenance-worker.controller.ts +++ b/server/src/maintenance/maintenance-worker.controller.ts @@ -1,7 +1,8 @@ -import { Body, Controller, Get, Post, Req, Res } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Post, Req, Res } from '@nestjs/common'; import { Request, Response } from 'express'; import { MaintenanceAuthDto, + MaintenanceListBackupsResponseDto, MaintenanceLoginDto, MaintenanceStatusResponseDto, SetMaintenanceModeDto, @@ -13,6 +14,7 @@ import { MaintenanceWorkerService } from 'src/maintenance/maintenance-worker.ser import { GetLoginDetails } from 'src/middleware/auth.guard'; import { LoginDetails } from 'src/services/auth.service'; import { respondWithCookie } from 'src/utils/response'; +import { FilenameParamDto } from 'src/validation'; @Controller() export class MaintenanceWorkerController { @@ -48,4 +50,16 @@ export class MaintenanceWorkerController { async setMaintenanceMode(@Body() dto: SetMaintenanceModeDto): Promise { await this.service.runAction(dto); } + + @Get('admin/maintenance/backups/list') + @MaintenanceRoute() + listBackups(): Promise { + return this.service.listBackups(); + } + + @Delete('admin/maintenance/backups/:filename') + @MaintenanceRoute() + async deleteBackup(@Param() { filename }: FilenameParamDto): Promise { + return this.service.deleteBackup(filename); + } } diff --git a/server/src/validation.ts b/server/src/validation.ts index 6d4bbfbe36..7dfb233780 100644 --- a/server/src/validation.ts +++ b/server/src/validation.ts @@ -96,6 +96,13 @@ export class UUIDAssetIDParamDto { assetId!: string; } +export class FilenameParamDto { + @IsNotEmpty() + @IsString() + @ApiProperty({ format: 'string' }) + filename!: string; +} + type PinCodeOptions = { optional?: boolean } & OptionalOptions; export const PinCode = (options?: PinCodeOptions & ApiPropertyOptions) => { const { optional, nullable, emptyToNull, ...apiPropertyOptions } = {