mirror of
https://github.com/immich-app/immich.git
synced 2025-12-23 17:25:11 +03:00
feat: upload backups
This commit is contained in:
@@ -1,5 +1,19 @@
|
||||
import { BadRequestException, Body, Controller, Delete, Get, Param, Post, Res } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
FileTypeValidator,
|
||||
Get,
|
||||
Param,
|
||||
ParseFilePipe,
|
||||
Post,
|
||||
Res,
|
||||
UploadedFile,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||
import { Response } from 'express';
|
||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
@@ -8,6 +22,7 @@ import {
|
||||
MaintenanceListBackupsResponseDto,
|
||||
MaintenanceLoginDto,
|
||||
MaintenanceStatusResponseDto,
|
||||
MaintenanceUploadBackupDto,
|
||||
SetMaintenanceModeDto,
|
||||
} from 'src/dtos/maintenance.dto';
|
||||
import { ApiTag, ImmichCookie, MaintenanceAction, Permission } from 'src/enum';
|
||||
@@ -104,4 +119,21 @@ export class MaintenanceController {
|
||||
values: [{ key: ImmichCookie.MaintenanceToken, value: jwt }],
|
||||
});
|
||||
}
|
||||
|
||||
@Post('backups/upload')
|
||||
@Authenticated({ permission: Permission.Maintenance, admin: true })
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@ApiBody({ description: 'Backup Upload', type: MaintenanceUploadBackupDto })
|
||||
@Endpoint({
|
||||
summary: 'Upload asset',
|
||||
description: 'Uploads a new asset to the server.',
|
||||
history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'),
|
||||
})
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
uploadBackup(
|
||||
@UploadedFile(new ParseFilePipe({ validators: [new FileTypeValidator({ fileType: 'application/gzip' })] }))
|
||||
file: Express.Multer.File,
|
||||
): Promise<void> {
|
||||
return this.service.uploadBackup(file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { MaintenanceAction } from 'src/enum';
|
||||
import { ValidateEnum, ValidateString } from 'src/validation';
|
||||
|
||||
@@ -31,3 +32,8 @@ export class MaintenanceListBackupsResponseDto {
|
||||
backups!: string[];
|
||||
failedBackups!: string[];
|
||||
}
|
||||
|
||||
export class MaintenanceUploadBackupDto {
|
||||
@ApiProperty({ type: 'string', format: 'binary', required: false })
|
||||
file?: any;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
import { Body, Controller, Delete, Get, Param, Post, Req, Res } from '@nestjs/common';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
FileTypeValidator,
|
||||
Get,
|
||||
Param,
|
||||
ParseFilePipe,
|
||||
Post,
|
||||
Req,
|
||||
Res,
|
||||
UploadedFile,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { Request, Response } from 'express';
|
||||
import {
|
||||
MaintenanceAuthDto,
|
||||
@@ -62,4 +76,14 @@ export class MaintenanceWorkerController {
|
||||
async deleteBackup(@Param() { filename }: FilenameParamDto): Promise<void> {
|
||||
return this.service.deleteBackup(filename);
|
||||
}
|
||||
|
||||
@Post('admin/maintenance/backups/upload')
|
||||
@MaintenanceRoute()
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
uploadBackup(
|
||||
@UploadedFile(new ParseFilePipe({ validators: [new FileTypeValidator({ fileType: 'application/gzip' })] }))
|
||||
file: Express.Multer.File,
|
||||
): Promise<void> {
|
||||
return this.service.uploadBackup(file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import { type ApiService as _ApiService } from 'src/services/api.service';
|
||||
import { type BaseService as _BaseService } from 'src/services/base.service';
|
||||
import { type ServerService as _ServerService } from 'src/services/server.service';
|
||||
import { MaintenanceModeState } from 'src/types';
|
||||
import { deleteBackup, listBackups, restoreBackup } from 'src/utils/backups';
|
||||
import { deleteBackup, listBackups, restoreBackup, uploadBackup } from 'src/utils/backups';
|
||||
import { getConfig } from 'src/utils/config';
|
||||
import { createMaintenanceLoginUrl } from 'src/utils/maintenance';
|
||||
import { getExternalDomain } from 'src/utils/misc';
|
||||
@@ -283,6 +283,10 @@ export class MaintenanceWorkerService {
|
||||
return deleteBackup(this.backupRepos, filename);
|
||||
}
|
||||
|
||||
async uploadBackup(file: Express.Multer.File): Promise<void> {
|
||||
return uploadBackup(file);
|
||||
}
|
||||
|
||||
private get backupRepos() {
|
||||
return {
|
||||
logger: this.logger,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { MaintenanceAuthDto, SetMaintenanceModeDto } from 'src/dtos/maintenance.
|
||||
import { MaintenanceAction, SystemMetadataKey } from 'src/enum';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { MaintenanceModeState } from 'src/types';
|
||||
import { deleteBackup, listBackups } from 'src/utils/backups';
|
||||
import { deleteBackup, listBackups, uploadBackup } from 'src/utils/backups';
|
||||
import { createMaintenanceLoginUrl, generateMaintenanceSecret, signMaintenanceJwt } from 'src/utils/maintenance';
|
||||
import { getExternalDomain } from 'src/utils/misc';
|
||||
|
||||
@@ -83,6 +83,10 @@ export class MaintenanceService extends BaseService {
|
||||
return deleteBackup(this.backupRepos, filename);
|
||||
}
|
||||
|
||||
async uploadBackup(file: Express.Multer.File): Promise<void> {
|
||||
return uploadBackup(file);
|
||||
}
|
||||
|
||||
private get backupRepos() {
|
||||
return {
|
||||
logger: this.logger,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { debounce } from 'lodash';
|
||||
import { DateTime } from 'luxon';
|
||||
import { stat, writeFile } from 'node:fs/promises';
|
||||
import path, { join } from 'node:path';
|
||||
import { PassThrough, Readable, Writable } from 'node:stream';
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
@@ -269,6 +271,18 @@ export async function listBackups({
|
||||
};
|
||||
}
|
||||
|
||||
export async function uploadBackup(file: Express.Multer.File): Promise<void> {
|
||||
const backupsFolder = StorageCore.getBaseFolder(StorageFolder.Backups);
|
||||
const path = join(backupsFolder, file.originalname);
|
||||
|
||||
try {
|
||||
await stat(path);
|
||||
throw new BadRequestException('File already exists!');
|
||||
} catch {
|
||||
await writeFile(path, file.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
function createSqlProgressStreams(cb: (progress: number) => void) {
|
||||
const STDIN_START_MARKER = new TextEncoder().encode('FROM stdin');
|
||||
const STDIN_END_MARKER = new TextEncoder().encode(String.raw`\.`);
|
||||
|
||||
Reference in New Issue
Block a user