Files
hedgedoc/backend/src/media/backends/s3-backend.ts
T
2026-01-14 19:46:27 +01:00

97 lines
3.0 KiB
TypeScript

/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { MediaBackendType } from '@hedgedoc/commons';
import { Inject, Injectable } from '@nestjs/common';
import { FileTypeResult } from 'file-type';
import { Client } from 'minio';
import { URL } from 'url';
import mediaConfiguration, {
MediaConfig,
S3MediaConfig,
} from '../../config/media.config';
import { MediaBackendError } from '../../errors/errors';
import { ConsoleLoggerService } from '../../logger/console-logger.service';
import { MediaBackend } from '../media-backend.interface';
@Injectable()
export class S3Backend implements MediaBackend {
private config: S3MediaConfig['s3'];
private client: Client;
private static determinePort(url: URL): number | undefined {
const port = parseInt(url.port);
return isNaN(port) ? undefined : port;
}
constructor(
private readonly logger: ConsoleLoggerService,
@Inject(mediaConfiguration.KEY)
private mediaConfig: MediaConfig,
) {
this.logger.setContext(S3Backend.name);
// only create the backend if s3 is configured
if (this.mediaConfig.backend.type !== MediaBackendType.S3) {
return;
}
this.config = this.mediaConfig.backend.s3;
const url = new URL(this.config.endpoint);
const isSecure = url.protocol === 'https:';
this.client = new Client({
endPoint: url.hostname,
port: S3Backend.determinePort(url),
useSSL: isSecure,
accessKey: this.config.accessKeyId,
secretKey: this.config.secretAccessKey,
pathStyle: this.config.pathStyle,
region: this.config.region,
});
}
async saveFile(
uuid: string,
buffer: Buffer,
fileType: FileTypeResult,
): Promise<null> {
try {
await this.client.putObject(
this.config.bucket,
uuid,
buffer,
buffer.length,
{
// oxlint-disable-next-line @typescript-eslint/naming-convention
'Content-Type': fileType.mime,
},
);
this.logger.log(`Uploaded file ${uuid}`, 'saveFile');
return null;
} catch (e) {
this.logger.error((e as Error).message, (e as Error).stack, 'saveFile');
throw new MediaBackendError(`Could not save file ${uuid}`);
}
}
async deleteFile(uuid: string, _: unknown): Promise<void> {
try {
await this.client.removeObject(this.config.bucket, uuid);
this.logger.log(`Deleted uploaded file ${uuid}`, 'deleteFile');
} catch (e) {
this.logger.error((e as Error).message, (e as Error).stack, 'deleteFile');
throw new MediaBackendError(`Could not delete file ${uuid}`);
}
}
async getFileUrl(uuid: string, _: unknown): Promise<string> {
try {
return await this.client.presignedGetObject(this.config.bucket, uuid);
} catch (e) {
this.logger.error((e as Error).message, (e as Error).stack, 'getFileUrl');
throw new MediaBackendError(`Could not get URL for file ${uuid}`);
}
}
}