Files
hedgedoc/backend/src/api/private/explore/explore.controller.ts
T
Philip Molares 3a4f2c8551 refactor(controller): improve api responses of controller methods
This should drastically improve how accurate our api document is.

Signed-off-by: Philip Molares <philip.molares@udo.edu>
2026-05-13 23:59:37 +02:00

166 lines
5.1 KiB
TypeScript

/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { NoteType, OptionalSortMode, SortMode } from '@hedgedoc/commons';
import {
BadRequestException,
Body,
Controller,
Get,
Param,
Put,
Query,
Res,
UseGuards,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { type FastifyReply } from 'fastify';
import { SessionGuard } from '../../../auth/session.guard';
import { NoteExploreEntryDto } from '../../../dtos/note-explore-entry.dto';
import { NotePinStatusDto } from '../../../dtos/note-pin-status.dto';
import { ExploreService } from '../../../explore/explore.service';
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
import { NoteService } from '../../../notes/note.service';
import { OpenApi } from '../../utils/decorators/openapi.decorator';
import { RequestUserId } from '../../utils/decorators/request-user-id.decorator';
type OptionalNoteType = NoteType | '';
@UseGuards(SessionGuard)
@OpenApi(401, 403, 429)
@ApiTags('explore')
@Controller('explore')
export class ExploreController {
constructor(
private readonly logger: ConsoleLoggerService,
private readonly exploreService: ExploreService,
private readonly noteService: NoteService,
) {
this.logger.setContext(ExploreController.name);
}
@Get('my')
@OpenApi(200)
getMyNotes(
@RequestUserId() userId: number,
@Query('page') page = 1,
@Query('sort') sort?: OptionalSortMode,
@Query('search') search?: string,
@Query('type') type?: OptionalNoteType,
): Promise<NoteExploreEntryDto[]> {
this.checkQueryParams(page, sort, type);
return this.exploreService.getMyNoteExploreEntries(userId, page, type, sort, search);
}
@Get('shared')
@OpenApi(200)
getSharedNotes(
@RequestUserId() userId: number,
@Query('page') page = 1,
@Query('sort') sort?: OptionalSortMode,
@Query('search') search?: string,
@Query('type') type?: OptionalNoteType,
): Promise<NoteExploreEntryDto[]> {
this.checkQueryParams(page, sort, type);
return this.exploreService.getSharedWithMeExploreEntries(userId, page, type, sort, search);
}
@Get('public')
@OpenApi(200)
getPublicNotes(
@Query('page') page = 1,
@Query('sort') sort?: OptionalSortMode,
@Query('search') search?: string,
@Query('type') type?: OptionalNoteType,
): Promise<NoteExploreEntryDto[]> {
this.checkQueryParams(page, sort, type);
return this.exploreService.getPublicNoteExploreEntries(page, type, sort, search);
}
@Get('pinned')
@OpenApi(200)
getMyPinnedNotes(@RequestUserId() userId: number): Promise<NoteExploreEntryDto[]> {
return this.exploreService.getMyPinnedNoteExploreEntries(userId);
}
@Get('visited')
@OpenApi(200)
getRecentlyVisitedNotes(
@RequestUserId() userId: number,
@Query('page') page = 1,
@Query('sort') sort?: OptionalSortMode,
@Query('search') search?: string,
@Query('type') type?: OptionalNoteType,
): Promise<NoteExploreEntryDto[]> {
this.checkQueryParams(page, sort, type);
return this.exploreService.getRecentlyVisitedNoteExploreEntries(
userId,
page,
type,
sort,
search,
);
}
@Put('pin/:noteAlias')
@OpenApi(200, 400, 404)
async setPinStatus(
@RequestUserId() userId: number,
@Param('noteAlias') noteAlias: string,
@Body() notePinStatusDto: NotePinStatusDto,
@Res() res: FastifyReply,
): Promise<NoteExploreEntryDto | null> {
const noteId = await this.noteService.getNoteIdByAlias(noteAlias);
const entry = await this.exploreService.setNotePinStatus(
userId,
noteId,
notePinStatusDto.isPinned,
);
// We send the response manually with res.send() and setting the content type accordingly.
// This is required to allow null to be sent
// https://github.com/nestjs/nest/issues/10415
res.header('Content-Type', 'application/json; charset=utf-8');
if (entry === null) {
res.send('null');
return null;
}
res.send(JSON.stringify(entry));
return entry;
}
private checkQueryParams(page: number, sort?: OptionalSortMode, type?: OptionalNoteType): void {
this.ensurePageNumberIsValid(page);
this.ensureTypeQueryParamIsValid(type);
this.ensureSortQueryParamIsValid(sort);
}
private ensurePageNumberIsValid(page: number): void {
if (page < 1 || Number.isNaN(page)) {
throw new BadRequestException('Page number must be greater than 0');
}
}
private ensureTypeQueryParamIsValid(type?: OptionalNoteType): void {
if (type === undefined || type === '') {
return;
}
const validValues = Object.values(NoteType);
if (!validValues.includes(type)) {
throw new BadRequestException(`Invalid note type in search: ${type}`);
}
}
private ensureSortQueryParamIsValid(sort?: OptionalSortMode): void {
if (sort === undefined || sort === '') {
return;
}
const validValues = Object.values(SortMode);
if (!validValues.includes(sort)) {
throw new BadRequestException(`Invalid sort mode in search: ${sort}`);
}
}
}