diff --git a/src/api/private/media/media.controller.ts b/src/api/private/media/media.controller.ts index 4664656b0..3f2a8983e 100644 --- a/src/api/private/media/media.controller.ts +++ b/src/api/private/media/media.controller.ts @@ -6,20 +6,26 @@ import { BadRequestException, Controller, + Delete, Headers, HttpCode, InternalServerErrorException, + NotFoundException, + Param, Post, + UnauthorizedException, UploadedFile, UseGuards, UseInterceptors, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; +import { ApiNoContentResponse } from '@nestjs/swagger'; import { ClientError, MediaBackendError, NotInDBError, + PermissionError, } from '../../../errors/errors'; import { SessionGuard } from '../../../identity/session.guard'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; @@ -29,7 +35,7 @@ import { MulterFile } from '../../../media/multer-file.interface'; import { Note } from '../../../notes/note.entity'; import { NotesService } from '../../../notes/notes.service'; import { User } from '../../../users/user.entity'; -import { UsersService } from '../../../users/users.service'; +import { successfullyDeletedDescription } from '../../utils/descriptions'; import { RequestUser } from '../../utils/request-user.decorator'; @UseGuards(SessionGuard) @@ -38,7 +44,6 @@ export class MediaController { constructor( private readonly logger: ConsoleLoggerService, private mediaService: MediaService, - private userService: UsersService, private noteService: NotesService, ) { this.logger.setContext(MediaController.name); @@ -73,4 +78,46 @@ export class MediaController { throw e; } } + + @Delete(':filename') + @HttpCode(204) + @ApiNoContentResponse({ description: successfullyDeletedDescription }) + async deleteMedia( + @RequestUser() user: User, + @Param('filename') filename: string, + ): Promise { + const username = user.username; + try { + this.logger.debug( + `Deleting '${filename}' for user '${username}'`, + 'deleteMedia', + ); + const mediaUpload = await this.mediaService.findUploadByFilename( + filename, + ); + if (mediaUpload.user.username !== username) { + this.logger.warn( + `${username} tried to delete '${filename}', but is not the owner`, + 'deleteMedia', + ); + throw new PermissionError( + `File '${filename}' is not owned by '${username}'`, + ); + } + await this.mediaService.deleteFile(mediaUpload); + } catch (e) { + if (e instanceof PermissionError) { + throw new UnauthorizedException(e.message); + } + 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/test/private-api/media.e2e-spec.ts b/test/private-api/media.e2e-spec.ts index 18a51d697..725421fce 100644 --- a/test/private-api/media.e2e-spec.ts +++ b/test/private-api/media.e2e-spec.ts @@ -24,9 +24,12 @@ import { IdentityService } from '../../src/identity/identity.service'; import { ConsoleLoggerService } from '../../src/logger/console-logger.service'; import { LoggerModule } from '../../src/logger/logger.module'; import { MediaModule } from '../../src/media/media.module'; +import { MediaService } from '../../src/media/media.service'; +import { Note } from '../../src/notes/note.entity'; import { NotesModule } from '../../src/notes/notes.module'; import { NotesService } from '../../src/notes/notes.service'; import { PermissionsModule } from '../../src/permissions/permissions.module'; +import { User } from '../../src/users/user.entity'; import { UsersService } from '../../src/users/users.service'; import { setupSessionMiddleware } from '../../src/utils/session'; import { ensureDeleted } from '../utils'; @@ -34,8 +37,11 @@ import { ensureDeleted } from '../utils'; describe('Media', () => { let identityService: IdentityService; let app: NestExpressApplication; + let mediaService: MediaService; let uploadPath: string; let agent: request.SuperAgentTest; + let testNote: Note; + let user: User; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ @@ -80,9 +86,13 @@ describe('Media', () => { app.useLogger(logger); identityService = moduleRef.get(IdentityService); const notesService: NotesService = moduleRef.get(NotesService); - await notesService.createNote('test content', 'test_upload_media'); const userService: UsersService = moduleRef.get(UsersService); - const user = await userService.createUser('hardcoded', 'Testy'); + user = await userService.createUser('hardcoded', 'Testy'); + testNote = await notesService.createNote( + 'test content', + 'test_upload_media', + ); + mediaService = moduleRef.get(MediaService); await identityService.createLocalIdentity(user, 'test'); agent = request.agent(app.getHttpServer()); await agent @@ -145,6 +155,13 @@ describe('Media', () => { }); }); + it('DELETE /media/{filename}', async () => { + const testImage = await fs.readFile('test/private-api/fixtures/test.png'); + const url = await mediaService.saveFile(testImage, user, testNote); + const filename = url.split('/').pop() || ''; + await agent.delete('/media/' + filename).expect(204); + }); + afterAll(async () => { // Delete the upload folder await ensureDeleted(uploadPath);