Merge pull request #953 from hedgedoc/mediaBackendErrors

This commit is contained in:
Yannick Bungers 2021-02-25 22:23:11 +01:00 committed by GitHub
commit 75be4611d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 49 additions and 4 deletions

View file

@ -9,6 +9,7 @@ import {
Controller, Controller,
Delete, Delete,
Headers, Headers,
InternalServerErrorException,
NotFoundException, NotFoundException,
Param, Param,
Post, Post,
@ -21,6 +22,7 @@ import {
import { FileInterceptor } from '@nestjs/platform-express'; import { FileInterceptor } from '@nestjs/platform-express';
import { import {
ClientError, ClientError,
MediaBackendError,
NotInDBError, NotInDBError,
PermissionError, PermissionError,
} from '../../../errors/errors'; } from '../../../errors/errors';
@ -66,6 +68,11 @@ export class MediaController {
if (e instanceof ClientError || e instanceof NotInDBError) { if (e instanceof ClientError || e instanceof NotInDBError) {
throw new BadRequestException(e.message); throw new BadRequestException(e.message);
} }
if (e instanceof MediaBackendError) {
throw new InternalServerErrorException(
'There was an error in the media backend',
);
}
throw e; throw e;
} }
} }
@ -86,6 +93,11 @@ export class MediaController {
if (e instanceof NotInDBError) { if (e instanceof NotInDBError) {
throw new NotFoundException(e.message); throw new NotFoundException(e.message);
} }
if (e instanceof MediaBackendError) {
throw new InternalServerErrorException(
'There was an error in the media backend',
);
}
throw e; throw e;
} }
} }

View file

@ -31,3 +31,7 @@ export class TooManyTokensError extends Error {
export class PermissionsUpdateInconsistentError extends Error { export class PermissionsUpdateInconsistentError extends Error {
name = 'PermissionsUpdateInconsistentError'; name = 'PermissionsUpdateInconsistentError';
} }
export class MediaBackendError extends Error {
name = 'MediaBackendError';
}

View file

@ -12,6 +12,7 @@ import { ConsoleLoggerService } from '../../logger/console-logger.service';
import { MediaBackend } from '../media-backend.interface'; import { MediaBackend } from '../media-backend.interface';
import { BackendData } from '../media-upload.entity'; import { BackendData } from '../media-upload.entity';
import { MediaConfig } from '../../config/media.config'; import { MediaConfig } from '../../config/media.config';
import { MediaBackendError } from '../../errors/errors';
@Injectable() @Injectable()
export class FilesystemBackend implements MediaBackend { export class FilesystemBackend implements MediaBackend {
@ -33,12 +34,23 @@ export class FilesystemBackend implements MediaBackend {
const filePath = this.getFilePath(fileName); const filePath = this.getFilePath(fileName);
this.logger.debug(`Writing file to: ${filePath}`, 'saveFile'); this.logger.debug(`Writing file to: ${filePath}`, 'saveFile');
await this.ensureDirectory(); await this.ensureDirectory();
try {
await fs.writeFile(filePath, buffer, null); await fs.writeFile(filePath, buffer, null);
return ['/' + filePath, 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<void> { async deleteFile(fileName: string, _: BackendData): Promise<void> {
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<string> { getFileURL(fileName: string, _: BackendData): Promise<string> {
@ -55,7 +67,17 @@ export class FilesystemBackend implements MediaBackend {
try { try {
await fs.access(this.uploadDirectory); await fs.access(this.uploadDirectory);
} catch (e) { } catch (e) {
try {
this.logger.debug(
`The directory '${this.uploadDirectory}' can't be accessed. Trying to create the directory`,
);
await fs.mkdir(this.uploadDirectory); await fs.mkdir(this.uploadDirectory);
} catch (e) {
this.logger.error(e.message, e.stack, 'deleteFile');
throw new MediaBackendError(
`Could not create '${this.uploadDirectory}'`,
);
}
} }
} }
} }

View file

@ -11,6 +11,7 @@ export interface MediaBackend {
* Saves a file according to backend internals. * Saves a file according to backend internals.
* @param buffer File data * @param buffer File data
* @param fileName Name of the file to save. Can include a file extension. * @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. * @return Tuple of file URL and internal backend data, which should be saved.
*/ */
saveFile(buffer: Buffer, fileName: string): Promise<[string, BackendData]>; saveFile(buffer: Buffer, fileName: string): Promise<[string, BackendData]>;
@ -19,6 +20,7 @@ export interface MediaBackend {
* Retrieve the URL of a previously saved file. * Retrieve the URL of a previously saved file.
* @param fileName String to identify the file * @param fileName String to identify the file
* @param backendData Internal backend data * @param backendData Internal backend data
* @throws {MediaBackendError} - there was an error deleting the file
*/ */
getFileURL(fileName: string, backendData: BackendData): Promise<string>; getFileURL(fileName: string, backendData: BackendData): Promise<string>;
@ -26,6 +28,7 @@ export interface MediaBackend {
* Delete a file from the backend * Delete a file from the backend
* @param fileName String to identify the file * @param fileName String to identify the file
* @param backendData Internal backend data * @param backendData Internal backend data
* @throws {MediaBackendError} - there was an error retrieving the url
*/ */
deleteFile(fileName: string, backendData: BackendData): Promise<void>; deleteFile(fileName: string, backendData: BackendData): Promise<void>;
} }

View file

@ -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. * @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 * @return {string} the url of the saved file
* @throws {ClientError} the MIME type of the file is not supported. * @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( async saveFile(
fileBuffer: Buffer, fileBuffer: Buffer,
@ -110,6 +112,7 @@ export class MediaService {
* @return {string} the url of the saved file * @return {string} the url of the saved file
* @throws {PermissionError} the user is not permitted to delete this file. * @throws {PermissionError} the user is not permitted to delete this file.
* @throws {NotInDBError} - the file entry specified is not in the database * @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<void> { async deleteFile(filename: string, username: string): Promise<void> {
this.logger.debug( this.logger.debug(
@ -136,6 +139,7 @@ export class MediaService {
* @param {string} filename - the name of the file entry to find * @param {string} filename - the name of the file entry to find
* @return {MediaUpload} the file entry, that was searched for * @return {MediaUpload} the file entry, that was searched for
* @throws {NotInDBError} - the file entry specified is not in the database * @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<MediaUpload> { async findUploadByFilename(filename: string): Promise<MediaUpload> {
const mediaUpload = await this.mediaUploadRepository.findOne(filename, { const mediaUpload = await this.mediaUploadRepository.findOne(filename, {