diff --git a/src/api/public/me/me.controller.ts b/src/api/public/me/me.controller.ts index c42d9a4fa..3bc2ae167 100644 --- a/src/api/public/me/me.controller.ts +++ b/src/api/public/me/me.controller.ts @@ -23,14 +23,25 @@ import { NoteMetadataDto } from '../../../notes/note-metadata.dto'; import { NotesService } from '../../../notes/notes.service'; import { UsersService } from '../../../users/users.service'; import { TokenAuthGuard } from '../../../auth/token-auth.guard'; -import { ApiSecurity, ApiTags } from '@nestjs/swagger'; +import { + ApiNoContentResponse, + ApiNotFoundResponse, + ApiOkResponse, + ApiSecurity, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; import { HistoryEntryDto } from '../../../history/history-entry.dto'; import { UserInfoDto } from '../../../users/user-info.dto'; import { NotInDBError } from '../../../errors/errors'; import { Request } from 'express'; import { MediaService } from '../../../media/media.service'; -import { MediaUploadUrlDto } from '../../../media/media-upload-url.dto'; import { MediaUploadDto } from '../../../media/media-upload.dto'; +import { + notFoundDescription, + successfullyDeletedDescription, + unauthorizedDescription, +} from '../../utils/descriptions'; @ApiTags('me') @ApiSecurity('token') @@ -48,6 +59,11 @@ export class MeController { @UseGuards(TokenAuthGuard) @Get() + @ApiOkResponse({ + description: 'The user information', + type: UserInfoDto, + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) async getMe(@Req() req: Request): Promise { return this.usersService.toUserDto( await this.usersService.getUserByUsername(req.user.userName), @@ -56,6 +72,12 @@ export class MeController { @UseGuards(TokenAuthGuard) @Get('history') + @ApiOkResponse({ + description: 'The history entries of the user', + isArray: true, + type: HistoryEntryDto, + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) async getUserHistory(@Req() req: Request): Promise { const foundEntries = await this.historyService.getEntriesByUser(req.user); return await Promise.all( @@ -65,6 +87,12 @@ export class MeController { @UseGuards(TokenAuthGuard) @Get('history/:note') + @ApiOkResponse({ + description: 'The history entry of the user which points to the note', + type: HistoryEntryDto, + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiNotFoundResponse({ description: notFoundDescription }) async getHistoryEntry( @Req() req: Request, @Param('note') note: string, @@ -85,6 +113,12 @@ export class MeController { @UseGuards(TokenAuthGuard) @Put('history/:note') + @ApiOkResponse({ + description: 'The updated history entry', + type: HistoryEntryDto, + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiNotFoundResponse({ description: notFoundDescription }) async updateHistoryEntry( @Req() req: Request, @Param('note') note: string, @@ -110,6 +144,9 @@ export class MeController { @UseGuards(TokenAuthGuard) @Delete('history/:note') @HttpCode(204) + @ApiNoContentResponse({ description: successfullyDeletedDescription }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiNotFoundResponse({ description: notFoundDescription }) async deleteHistoryEntry( @Req() req: Request, @Param('note') note: string, @@ -127,6 +164,12 @@ export class MeController { @UseGuards(TokenAuthGuard) @Get('notes') + @ApiOkResponse({ + description: 'Metadata of all notes of the user', + isArray: true, + type: NoteMetadataDto, + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) async getMyNotes(@Req() req: Request): Promise { const notes = this.notesService.getUserNotes(req.user); return await Promise.all( @@ -136,6 +179,12 @@ export class MeController { @UseGuards(TokenAuthGuard) @Get('media') + @ApiOkResponse({ + description: 'All media uploads of the user', + isArray: true, + type: MediaUploadDto, + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) async getMyMedia(@Req() req: Request): Promise { const media = await this.mediaService.listUploadsByUser(req.user); return media.map((media) => this.mediaService.toMediaUploadDto(media)); diff --git a/src/api/public/media/media.controller.ts b/src/api/public/media/media.controller.ts index 28fff33c3..388121045 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, + HttpCode, InternalServerErrorException, NotFoundException, Param, @@ -31,8 +32,25 @@ import { ConsoleLoggerService } from '../../../logger/console-logger.service'; import { MediaService } from '../../../media/media.service'; import { MulterFile } from '../../../media/multer-file.interface'; import { TokenAuthGuard } from '../../../auth/token-auth.guard'; -import { ApiSecurity, ApiTags } from '@nestjs/swagger'; +import { + ApiBody, + ApiConsumes, + ApiCreatedResponse, + ApiForbiddenResponse, + ApiHeader, + ApiNoContentResponse, + ApiNotFoundResponse, + ApiSecurity, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; import { MediaUploadUrlDto } from '../../../media/media-upload-url.dto'; +import { + forbiddenDescription, + notFoundDescription, + successfullyDeletedDescription, + unauthorizedDescription, +} from '../../utils/descriptions'; @ApiTags('media') @ApiSecurity('token') @@ -47,7 +65,22 @@ export class MediaController { @UseGuards(TokenAuthGuard) @Post() + @ApiConsumes('multipart/form-data') + @ApiBody({ + description: 'The binary file to upload', + }) + @ApiHeader({ + name: 'HedgeDoc-Note', + description: 'ID or alias of the parent note', + }) + @ApiCreatedResponse({ + description: 'The file was uploaded successfully', + type: MediaUploadUrlDto, + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiForbiddenResponse({ description: forbiddenDescription }) @UseInterceptors(FileInterceptor('file')) + @HttpCode(201) async uploadMedia( @Req() req: Request, @UploadedFile() file: MulterFile, @@ -80,6 +113,11 @@ export class MediaController { @UseGuards(TokenAuthGuard) @Delete(':filename') + @HttpCode(204) + @ApiNoContentResponse({ description: successfullyDeletedDescription }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiForbiddenResponse({ description: forbiddenDescription }) + @ApiNotFoundResponse({ description: notFoundDescription }) async deleteMedia( @Req() req: Request, @Param('filename') filename: string, diff --git a/src/api/public/monitoring/monitoring.controller.ts b/src/api/public/monitoring/monitoring.controller.ts index 1905adb08..6f93e04ad 100644 --- a/src/api/public/monitoring/monitoring.controller.ts +++ b/src/api/public/monitoring/monitoring.controller.ts @@ -7,8 +7,19 @@ import { Controller, Get, UseGuards } from '@nestjs/common'; import { MonitoringService } from '../../../monitoring/monitoring.service'; import { TokenAuthGuard } from '../../../auth/token-auth.guard'; -import { ApiSecurity, ApiTags } from '@nestjs/swagger'; +import { + ApiForbiddenResponse, + ApiOkResponse, + ApiProduces, + ApiSecurity, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; import { ServerStatusDto } from '../../../monitoring/server-status.dto'; +import { + forbiddenDescription, + unauthorizedDescription, +} from '../../utils/descriptions'; @ApiTags('monitoring') @ApiSecurity('token') @@ -18,6 +29,12 @@ export class MonitoringController { @UseGuards(TokenAuthGuard) @Get() + @ApiOkResponse({ + description: 'The server info', + type: ServerStatusDto, + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiForbiddenResponse({ description: forbiddenDescription }) getStatus(): Promise { // TODO: toServerStatusDto. return this.monitoringService.getServerStatus(); @@ -25,6 +42,12 @@ export class MonitoringController { @UseGuards(TokenAuthGuard) @Get('prometheus') + @ApiOkResponse({ + description: 'Prometheus compatible monitoring data', + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiForbiddenResponse({ description: forbiddenDescription }) + @ApiProduces('text/plain') getPrometheusStatus(): string { return ''; } diff --git a/src/api/public/notes/notes.controller.ts b/src/api/public/notes/notes.controller.ts index bfc398908..2185284d0 100644 --- a/src/api/public/notes/notes.controller.ts +++ b/src/api/public/notes/notes.controller.ts @@ -11,6 +11,7 @@ import { Delete, Get, Header, + HttpCode, NotFoundException, Param, Post, @@ -34,7 +35,17 @@ import { NotesService } from '../../../notes/notes.service'; import { RevisionsService } from '../../../revisions/revisions.service'; import { MarkdownBody } from '../../utils/markdownbody-decorator'; import { TokenAuthGuard } from '../../../auth/token-auth.guard'; -import { ApiSecurity, ApiTags } from '@nestjs/swagger'; +import { + ApiCreatedResponse, + ApiForbiddenResponse, + ApiNoContentResponse, + ApiNotFoundResponse, + ApiOkResponse, + ApiProduces, + ApiSecurity, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; import { HistoryService } from '../../../history/history.service'; import { NoteDto } from '../../../notes/note.dto'; import { NoteMetadataDto } from '../../../notes/note-metadata.dto'; @@ -43,6 +54,12 @@ import { RevisionDto } from '../../../revisions/revision.dto'; import { PermissionsService } from '../../../permissions/permissions.service'; import { Note } from '../../../notes/note.entity'; import { Request } from 'express'; +import { + forbiddenDescription, + notFoundDescription, + successfullyDeletedDescription, + unauthorizedDescription, +} from '../../utils/descriptions'; @ApiTags('notes') @ApiSecurity('token') @@ -60,6 +77,9 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Post() + @HttpCode(201) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiForbiddenResponse({ description: forbiddenDescription }) async createNote( @Req() req: Request, @MarkdownBody() text: string, @@ -76,6 +96,13 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Get(':noteIdOrAlias') + @ApiOkResponse({ + description: 'Get information about the newly created note', + type: NoteDto, + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiForbiddenResponse({ description: forbiddenDescription }) + @ApiNotFoundResponse({ description: notFoundDescription }) async getNote( @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, @@ -101,6 +128,13 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Post(':noteAlias') + @HttpCode(201) + @ApiCreatedResponse({ + description: 'Get information about the newly created note', + type: NoteDto, + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiForbiddenResponse({ description: forbiddenDescription }) async createNamedNote( @Req() req: Request, @Param('noteAlias') noteAlias: string, @@ -127,6 +161,11 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Delete(':noteIdOrAlias') + @HttpCode(204) + @ApiNoContentResponse({ description: successfullyDeletedDescription }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiForbiddenResponse({ description: forbiddenDescription }) + @ApiNotFoundResponse({ description: notFoundDescription }) async deleteNote( @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, @@ -153,6 +192,13 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Put(':noteIdOrAlias') + @ApiOkResponse({ + description: 'The new, changed note', + type: NoteDto, + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiForbiddenResponse({ description: forbiddenDescription }) + @ApiNotFoundResponse({ description: notFoundDescription }) async updateNote( @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, @@ -180,6 +226,13 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Get(':noteIdOrAlias/content') + @ApiProduces('text/markdown') + @ApiOkResponse({ + description: 'The raw markdown content of the note', + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiForbiddenResponse({ description: forbiddenDescription }) + @ApiNotFoundResponse({ description: notFoundDescription }) @Header('content-type', 'text/markdown') async getNoteContent( @Req() req: Request, @@ -204,6 +257,13 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Get(':noteIdOrAlias/metadata') + @ApiOkResponse({ + description: 'The metadata of the note', + type: NoteMetadataDto, + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiForbiddenResponse({ description: forbiddenDescription }) + @ApiNotFoundResponse({ description: notFoundDescription }) async getNoteMetadata( @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, @@ -230,6 +290,13 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Put(':noteIdOrAlias/metadata/permissions') + @ApiOkResponse({ + description: 'The updated permissions of the note', + type: NotePermissionsDto, + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiForbiddenResponse({ description: forbiddenDescription }) + @ApiNotFoundResponse({ description: notFoundDescription }) async updateNotePermissions( @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, @@ -256,6 +323,14 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Get(':noteIdOrAlias/revisions') + @ApiOkResponse({ + description: 'Revisions of the note', + isArray: true, + type: RevisionMetadataDto, + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiForbiddenResponse({ description: forbiddenDescription }) + @ApiNotFoundResponse({ description: notFoundDescription }) async getNoteRevisions( @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, @@ -284,6 +359,13 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Get(':noteIdOrAlias/revisions/:revisionId') + @ApiOkResponse({ + description: 'Revision of the note for the given id or alias', + type: RevisionDto, + }) + @ApiUnauthorizedResponse({ description: unauthorizedDescription }) + @ApiForbiddenResponse({ description: forbiddenDescription }) + @ApiNotFoundResponse({ description: notFoundDescription }) async getNoteRevision( @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, diff --git a/test/public-api/media.e2e-spec.ts b/test/public-api/media.e2e-spec.ts index 0c5d67ad1..a3d15d622 100644 --- a/test/public-api/media.e2e-spec.ts +++ b/test/public-api/media.e2e-spec.ts @@ -104,7 +104,7 @@ describe('Notes', () => { const filename = url.split('/').pop(); await request(app.getHttpServer()) .delete('/media/' + filename) - .expect(200); + .expect(204); }); afterAll(async () => { diff --git a/test/public-api/notes.e2e-spec.ts b/test/public-api/notes.e2e-spec.ts index 5a5b5537a..155ca9c86 100644 --- a/test/public-api/notes.e2e-spec.ts +++ b/test/public-api/notes.e2e-spec.ts @@ -146,7 +146,7 @@ describe('Notes', () => { describe('DELETE /notes/{note}', () => { it('works with an existing alias', async () => { await notesService.createNote(content, 'test3', user); - await request(app.getHttpServer()).delete('/notes/test3').expect(200); + await request(app.getHttpServer()).delete('/notes/test3').expect(204); await expect(notesService.getNoteByIdOrAlias('test3')).rejects.toEqual( new NotInDBError("Note with id/alias 'test3' not found."), );