S3Backend: Add S3 MediaBackend

Add minio dependency

Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
Philip Molares 2021-02-23 16:14:14 +01:00
parent cf6c08e3d6
commit 2d98e2f8b4
5 changed files with 186 additions and 11 deletions

View file

@ -0,0 +1,79 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import mediaConfiguration from '../../config/media.config';
import { ConsoleLoggerService } from '../../logger/console-logger.service';
import { MediaBackend } from '../media-backend.interface';
import { BackendData } from '../media-upload.entity';
import { MediaConfig } from '../../config/media.config';
import { Client } from 'minio';
import { BackendType } from './backend-type.enum';
import { MediaBackendError } from '../../errors/errors';
@Injectable()
export class S3Backend implements MediaBackend {
private config: MediaConfig['backend']['s3'];
private client: Client;
constructor(
private readonly logger: ConsoleLoggerService,
@Inject(mediaConfiguration.KEY)
private mediaConfig: MediaConfig,
) {
this.logger.setContext(S3Backend.name);
if (mediaConfig.backend.use === BackendType.S3) {
this.config = mediaConfig.backend.s3;
const url = new URL(this.config.endPoint);
const secure = url.protocol === 'https:'; // url.protocol contains a trailing ':'
const endpoint = `${url.hostname}${url.pathname}`;
let port = parseInt(url.port);
if (isNaN(port)) {
port = secure ? 443 : 80;
}
this.client = new Client({
endPoint: endpoint.substr(0, endpoint.length - 1), // remove trailing '/'
port: port,
useSSL: secure,
accessKey: this.config.accessKeyId,
secretKey: this.config.secretAccessKey,
});
}
}
async saveFile(
buffer: Buffer,
fileName: string,
): Promise<[string, BackendData]> {
try {
await this.client.putObject(this.config.bucket, fileName, buffer);
this.logger.log(`Uploaded file ${fileName}`, 'saveFile');
return [this.getUrl(fileName), null];
} catch (e) {
this.logger.error((e as Error).message, (e as Error).stack, 'saveFile');
throw new MediaBackendError(`Could not save '${fileName}' on S3`);
}
}
async deleteFile(fileName: string, _: BackendData): Promise<void> {
try {
await this.client.removeObject(this.config.bucket, fileName);
const url = this.getUrl(fileName);
this.logger.log(`Deleted ${url}`, 'saveFile');
return;
} catch (e) {
this.logger.error((e as Error).message, (e as Error).stack, 'saveFile');
throw new MediaBackendError(`Could not delete '${fileName}' on S3`);
}
}
private getUrl(fileName: string): string {
const url = new URL(this.config.endPoint);
const port = url.port !== '' ? `:${url.port}` : '';
const bucket = this.config.bucket;
return `${url.protocol}//${url.hostname}${port}${url.pathname}${bucket}/${fileName}`;
}
}

View file

@ -13,6 +13,7 @@ import { UsersModule } from '../users/users.module';
import { FilesystemBackend } from './backends/filesystem-backend';
import { MediaUpload } from './media-upload.entity';
import { MediaService } from './media.service';
import { S3Backend } from './backends/s3-backend';
import { ImgurBackend } from './backends/imgur-backend';
import { AzureBackend } from './backends/azure-backend';
@ -24,7 +25,13 @@ import { AzureBackend } from './backends/azure-backend';
LoggerModule,
ConfigModule,
],
providers: [MediaService, FilesystemBackend, AzureBackend, ImgurBackend],
providers: [
MediaService,
FilesystemBackend,
AzureBackend,
ImgurBackend,
S3Backend,
],
exports: [MediaService],
})
export class MediaModule {}

View file

@ -19,6 +19,7 @@ import { FilesystemBackend } from './backends/filesystem-backend';
import { MediaBackend } from './media-backend.interface';
import { MediaUpload } from './media-upload.entity';
import { MediaUploadUrlDto } from './media-upload-url.dto';
import { S3Backend } from './backends/s3-backend';
import { AzureBackend } from './backends/azure-backend';
import { ImgurBackend } from './backends/imgur-backend';
@ -164,6 +165,8 @@ export class MediaService {
return BackendType.AZURE;
case 'imgur':
return BackendType.IMGUR;
case 's3':
return BackendType.S3;
}
}
@ -171,6 +174,8 @@ export class MediaService {
switch (type) {
case BackendType.FILESYSTEM:
return this.moduleRef.get(FilesystemBackend);
case BackendType.S3:
return this.moduleRef.get(S3Backend);
case BackendType.AZURE:
return this.moduleRef.get(AzureBackend);
case BackendType.IMGUR: