2021-02-23 10:14:14 -05:00
|
|
|
/*
|
|
|
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
import { Inject, Injectable } from '@nestjs/common';
|
2021-08-29 12:45:46 -04:00
|
|
|
import { Client } from 'minio';
|
2021-09-06 10:32:24 -04:00
|
|
|
import { URL } from 'url';
|
2021-08-29 12:45:46 -04:00
|
|
|
|
2021-08-29 12:57:00 -04:00
|
|
|
import mediaConfiguration, { MediaConfig } from '../../config/media.config';
|
2021-08-29 12:45:46 -04:00
|
|
|
import { MediaBackendError } from '../../errors/errors';
|
2021-02-23 10:14:14 -05:00
|
|
|
import { ConsoleLoggerService } from '../../logger/console-logger.service';
|
|
|
|
import { MediaBackend } from '../media-backend.interface';
|
|
|
|
import { BackendData } from '../media-upload.entity';
|
|
|
|
import { BackendType } from './backend-type.enum';
|
|
|
|
|
|
|
|
@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);
|
2023-04-14 04:43:45 -04:00
|
|
|
if (mediaConfig.backend.use !== BackendType.S3) {
|
|
|
|
return;
|
2021-02-23 10:14:14 -05:00
|
|
|
}
|
2023-04-14 04:43:45 -04:00
|
|
|
this.config = mediaConfig.backend.s3;
|
|
|
|
const url = new URL(this.config.endPoint);
|
|
|
|
const isSecure = url.protocol === 'https:';
|
|
|
|
this.client = new Client({
|
|
|
|
endPoint: url.hostname,
|
|
|
|
port: this.determinePort(url),
|
|
|
|
useSSL: isSecure,
|
|
|
|
accessKey: this.config.accessKeyId,
|
|
|
|
secretKey: this.config.secretAccessKey,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private determinePort(url: URL): number | undefined {
|
|
|
|
const port = parseInt(url.port);
|
|
|
|
return isNaN(port) ? undefined : port;
|
2021-02-23 10:14:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-14 12:05:14 -04:00
|
|
|
async deleteFile(fileName: string): Promise<void> {
|
2021-02-23 10:14:14 -05:00
|
|
|
try {
|
|
|
|
await this.client.removeObject(this.config.bucket, fileName);
|
|
|
|
const url = this.getUrl(fileName);
|
2021-05-02 15:53:28 -04:00
|
|
|
this.logger.log(`Deleted ${url}`, 'deleteFile');
|
2021-02-23 10:14:14 -05:00
|
|
|
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);
|
2023-04-14 12:07:44 -04:00
|
|
|
if (!url.pathname.endsWith('/')) {
|
|
|
|
url.pathname += '/';
|
|
|
|
}
|
|
|
|
url.pathname += `${this.config.bucket}/${fileName}`;
|
|
|
|
return url.toString();
|
2021-02-23 10:14:14 -05:00
|
|
|
}
|
|
|
|
}
|