diff --git a/src/api/public/media/media.controller.ts b/src/api/public/media/media.controller.ts index 436e87b4c..4520e6b91 100644 --- a/src/api/public/media/media.controller.ts +++ b/src/api/public/media/media.controller.ts @@ -9,6 +9,7 @@ import { Controller, Delete, Headers, + InternalServerErrorException, NotFoundException, Param, Post, @@ -21,6 +22,7 @@ import { import { FileInterceptor } from '@nestjs/platform-express'; import { ClientError, + MediaBackendError, NotInDBError, PermissionError, } from '../../../errors/errors'; @@ -66,6 +68,11 @@ export class MediaController { if (e instanceof ClientError || e instanceof NotInDBError) { throw new BadRequestException(e.message); } + if (e instanceof MediaBackendError) { + throw new InternalServerErrorException( + 'There was an error in the media backend', + ); + } throw e; } } @@ -86,6 +93,11 @@ export class MediaController { if (e instanceof NotInDBError) { throw new NotFoundException(e.message); } + if (e instanceof MediaBackendError) { + throw new InternalServerErrorException( + 'There was an error in the media backend', + ); + } throw e; } } diff --git a/src/errors/errors.ts b/src/errors/errors.ts index fca24a873..aaef8afc3 100644 --- a/src/errors/errors.ts +++ b/src/errors/errors.ts @@ -31,3 +31,7 @@ export class TooManyTokensError extends Error { export class PermissionsUpdateInconsistentError extends Error { name = 'PermissionsUpdateInconsistentError'; } + +export class MediaBackendError extends Error { + name = 'MediaBackendError'; +} diff --git a/src/media/backends/filesystem-backend.ts b/src/media/backends/filesystem-backend.ts index 505b94ea6..918fd450b 100644 --- a/src/media/backends/filesystem-backend.ts +++ b/src/media/backends/filesystem-backend.ts @@ -12,6 +12,7 @@ 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 { MediaBackendError } from '../../errors/errors'; @Injectable() export class FilesystemBackend implements MediaBackend { @@ -33,12 +34,23 @@ export class FilesystemBackend implements MediaBackend { const filePath = this.getFilePath(fileName); this.logger.debug(`Writing file to: ${filePath}`, 'saveFile'); await this.ensureDirectory(); - await fs.writeFile(filePath, buffer, null); - return ['/' + filePath, null]; + try { + await fs.writeFile(filePath, buffer, null); + return ['/' + filePath, null]; + } catch (e) { + this.logger.error(e.message, e.stack, 'saveFile'); + throw new MediaBackendError(`Could not save '${filePath}'`); + } } async deleteFile(fileName: string, _: BackendData): Promise { - return fs.unlink(this.getFilePath(fileName)); + const filePath = this.getFilePath(fileName); + try { + return fs.unlink(filePath); + } catch (e) { + this.logger.error(e.message, e.stack, 'deleteFile'); + throw new MediaBackendError(`Could not delete '${filePath}'`); + } } getFileURL(fileName: string, _: BackendData): Promise { @@ -55,7 +67,17 @@ export class FilesystemBackend implements MediaBackend { try { await fs.access(this.uploadDirectory); } catch (e) { - await fs.mkdir(this.uploadDirectory); + try { + this.logger.debug( + `The directory '${this.uploadDirectory}' can't be accessed. Trying to create the directory`, + ); + await fs.mkdir(this.uploadDirectory); + } catch (e) { + this.logger.error(e.message, e.stack, 'deleteFile'); + throw new MediaBackendError( + `Could not create '${this.uploadDirectory}'`, + ); + } } } } diff --git a/src/media/media-backend.interface.ts b/src/media/media-backend.interface.ts index 8de2c42fd..634571392 100644 --- a/src/media/media-backend.interface.ts +++ b/src/media/media-backend.interface.ts @@ -11,6 +11,7 @@ export interface MediaBackend { * Saves a file according to backend internals. * @param buffer File data * @param fileName Name of the file to save. Can include a file extension. + * @throws {MediaBackendError} - there was an error saving the file * @return Tuple of file URL and internal backend data, which should be saved. */ saveFile(buffer: Buffer, fileName: string): Promise<[string, BackendData]>; @@ -19,6 +20,7 @@ export interface MediaBackend { * Retrieve the URL of a previously saved file. * @param fileName String to identify the file * @param backendData Internal backend data + * @throws {MediaBackendError} - there was an error deleting the file */ getFileURL(fileName: string, backendData: BackendData): Promise; @@ -26,6 +28,7 @@ export interface MediaBackend { * Delete a file from the backend * @param fileName String to identify the file * @param backendData Internal backend data + * @throws {MediaBackendError} - there was an error retrieving the url */ deleteFile(fileName: string, backendData: BackendData): Promise; } diff --git a/src/media/media.service.ts b/src/media/media.service.ts index 2272c0a06..3869c6da6 100644 --- a/src/media/media.service.ts +++ b/src/media/media.service.ts @@ -67,6 +67,8 @@ export class MediaService { * @param {string} noteId - the id or alias of the note which will be associated with the new file. * @return {string} the url of the saved file * @throws {ClientError} the MIME type of the file is not supported. + * @throws {NotInDBError} - the note or user is not in the database + * @throws {MediaBackendError} - there was an error saving the file */ async saveFile( fileBuffer: Buffer, @@ -110,6 +112,7 @@ export class MediaService { * @return {string} the url of the saved file * @throws {PermissionError} the user is not permitted to delete this file. * @throws {NotInDBError} - the file entry specified is not in the database + * @throws {MediaBackendError} - there was an error deleting the file */ async deleteFile(filename: string, username: string): Promise { this.logger.debug( @@ -136,6 +139,7 @@ export class MediaService { * @param {string} filename - the name of the file entry to find * @return {MediaUpload} the file entry, that was searched for * @throws {NotInDBError} - the file entry specified is not in the database + * @throws {MediaBackendError} - there was an error retrieving the url */ async findUploadByFilename(filename: string): Promise { const mediaUpload = await this.mediaUploadRepository.findOne(filename, {