From 32c8df6a4c65b37dcdf1e699f8d1098316aa2076 Mon Sep 17 00:00:00 2001 From: andres Date: Sun, 21 Jan 2024 15:52:03 +0100 Subject: [PATCH] feat: add get /decks v2 which does not return maxCardsCount. Add /decks/min-max-cards to use instead. /v1/decks is now deprecated --- src/main.ts | 7 ++- src/modules/decks/decks.controller.ts | 43 ++++++++++++++++--- src/modules/decks/decks.module.ts | 8 +++- src/modules/decks/entities/deck.entity.ts | 7 ++- .../decks/entities/min-max-cards.entity.ts | 4 ++ .../decks/infrastructure/decks.repository.ts | 13 ++++-- .../use-cases/get-all-decks-use-case-v1.ts | 21 +++++++++ ...e-case.ts => get-all-decks-use-case-v2.ts} | 8 ++-- .../use-cases/get-min-max-cards-use-case.ts | 17 ++++++++ src/modules/decks/use-cases/index.ts | 4 +- 10 files changed, 114 insertions(+), 18 deletions(-) create mode 100644 src/modules/decks/entities/min-max-cards.entity.ts create mode 100644 src/modules/decks/use-cases/get-all-decks-use-case-v1.ts rename src/modules/decks/use-cases/{get-all-decks-use-case.ts => get-all-decks-use-case-v2.ts} (64%) create mode 100644 src/modules/decks/use-cases/get-min-max-cards-use-case.ts diff --git a/src/main.ts b/src/main.ts index a7a2182..7445da9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import { Logger } from '@nestjs/common' +import { Logger, VersioningType } from '@nestjs/common' import { NestFactory } from '@nestjs/core' import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger' import * as cookieParser from 'cookie-parser' @@ -16,7 +16,10 @@ async function bootstrap() { }) app.use(cookieParser()) - app.setGlobalPrefix('v1') + app.enableVersioning({ + type: VersioningType.URI, + defaultVersion: '1', + }) const config = new DocumentBuilder() .setTitle('Flashcards') .setDescription('Flashcards API') diff --git a/src/modules/decks/decks.controller.ts b/src/modules/decks/decks.controller.ts index 6bce2cc..11a00fa 100644 --- a/src/modules/decks/decks.controller.ts +++ b/src/modules/decks/decks.controller.ts @@ -14,6 +14,7 @@ import { UploadedFiles, UseGuards, UseInterceptors, + Version, } from '@nestjs/common' import { CommandBus } from '@nestjs/cqrs' import { FileFieldsInterceptor } from '@nestjs/platform-express' @@ -35,14 +36,17 @@ import { Card, CardWithGrade, PaginatedCardsWithGrade } from '../cards/entities/ import { CreateDeckDto, GetAllDecksDto, UpdateDeckDto } from './dto' import { GetRandomCardDto } from './dto/get-random-card.dto' -import { Deck, DeckWithAuthor, PaginatedDecks } from './entities/deck.entity' +import { Deck, DeckWithAuthor, PaginatedDecksWithMaxCardsCount } from './entities/deck.entity' +import { MinMaxCards } from './entities/min-max-cards.entity' import { CreateCardCommand, CreateDeckCommand, DeleteDeckByIdCommand, GetAllCardsInDeckCommand, - GetAllDecksCommand, + GetAllDecksV1Command, + GetAllDecksV2Command, GetDeckByIdCommand, + GetMinMaxCardsUseCaseCommand, GetRandomCardInDeckCommand, SaveGradeCommand, UpdateDeckCommand, @@ -54,14 +58,43 @@ export class DecksController { constructor(private commandBus: CommandBus) {} @HttpCode(HttpStatus.PARTIAL_CONTENT) - @ApiOperation({ description: 'Retrieve paginated decks list.', summary: 'Paginated decks list' }) + @ApiOperation({ + description: 'Deprecated. Use v2 in combination with /min-max-cards request', + summary: 'Paginated decks list', + deprecated: true, + }) @ApiUnauthorizedResponse({ description: 'Unauthorized' }) @UseGuards(JwtAuthGuard) @Get() - findAll(@Query() query: GetAllDecksDto, @Req() req): Promise { + findAllV1(@Query() query: GetAllDecksDto, @Req() req): Promise { const finalQuery = Pagination.getPaginationData(query) - return this.commandBus.execute(new GetAllDecksCommand({ ...finalQuery, userId: req.user.id })) + return this.commandBus.execute(new GetAllDecksV1Command({ ...finalQuery, userId: req.user.id })) + } + + @HttpCode(HttpStatus.PARTIAL_CONTENT) + @ApiOperation({ description: 'Retrieve paginated decks list.', summary: 'Paginated decks list' }) + @ApiUnauthorizedResponse({ description: 'Unauthorized' }) + @UseGuards(JwtAuthGuard) + @Version('2') + @Get() + findAllV2(@Query() query: GetAllDecksDto, @Req() req): Promise { + const finalQuery = Pagination.getPaginationData(query) + + return this.commandBus.execute(new GetAllDecksV2Command({ ...finalQuery, userId: req.user.id })) + } + + @HttpCode(HttpStatus.OK) + @ApiOperation({ + description: 'Retrieve the minimum and maximum amount of cards in a deck.', + summary: 'Minimum and maximum amount of cards in a deck', + }) + @ApiUnauthorizedResponse({ description: 'Unauthorized' }) + @UseGuards(JwtAuthGuard) + @Version('2') + @Get('min-max-cards') + findMinMaxCards(): Promise { + return this.commandBus.execute(new GetMinMaxCardsUseCaseCommand()) } @ApiConsumes('multipart/form-data') diff --git a/src/modules/decks/decks.module.ts b/src/modules/decks/decks.module.ts index 33ab10b..6a0deed 100644 --- a/src/modules/decks/decks.module.ts +++ b/src/modules/decks/decks.module.ts @@ -12,7 +12,9 @@ import { CreateDeckHandler, DeleteDeckByIdHandler, GetDeckByIdHandler, - GetAllDecksHandler, + GetAllDecksV1Handler, + GetMinMaxCardsUseCaseHandler, + GetAllDecksV2Handler, UpdateDeckHandler, GetAllCardsInDeckHandler, CreateCardHandler, @@ -22,10 +24,12 @@ import { const commandHandlers = [ CreateDeckHandler, - GetAllDecksHandler, + GetAllDecksV1Handler, + GetAllDecksV2Handler, GetDeckByIdHandler, GetRandomCardInDeckHandler, DeleteDeckByIdHandler, + GetMinMaxCardsUseCaseHandler, UpdateDeckHandler, GetAllCardsInDeckHandler, CreateCardHandler, diff --git a/src/modules/decks/entities/deck.entity.ts b/src/modules/decks/entities/deck.entity.ts index 1a1c0f1..c34ed5c 100644 --- a/src/modules/decks/entities/deck.entity.ts +++ b/src/modules/decks/entities/deck.entity.ts @@ -20,8 +20,13 @@ export class DeckAuthor { name: string } -export class PaginatedDecks { +export class PaginatedDecksWithMaxCardsCount { items: DeckWithAuthor[] pagination: Pagination maxCardsCount: number } + +export class PaginatedDecks { + items: DeckWithAuthor[] + pagination: Pagination +} diff --git a/src/modules/decks/entities/min-max-cards.entity.ts b/src/modules/decks/entities/min-max-cards.entity.ts new file mode 100644 index 0000000..439f28d --- /dev/null +++ b/src/modules/decks/entities/min-max-cards.entity.ts @@ -0,0 +1,4 @@ +export class MinMaxCards { + max: number + min: number +} diff --git a/src/modules/decks/infrastructure/decks.repository.ts b/src/modules/decks/infrastructure/decks.repository.ts index 22b1595..bc096ad 100644 --- a/src/modules/decks/infrastructure/decks.repository.ts +++ b/src/modules/decks/infrastructure/decks.repository.ts @@ -57,6 +57,16 @@ export class DecksRepository { } } + async findMinMaxCards(): Promise<{ min: number; max: number }> { + const result = await this.prisma + .$queryRaw`SELECT MAX(card_count) as maxCardsCount, MIN(card_count) as minCardsCount FROM (SELECT deck.id, COUNT(card.id) as card_count FROM deck LEFT JOIN card ON deck.id = card.deckId GROUP BY deck.id) AS card_counts;` + + return { + max: Number(result[0].maxCardsCount), + min: Number(result[0].minCardsCount), + } + } + async findAllDecks({ name = undefined, authorId = undefined, @@ -190,8 +200,6 @@ export class DecksRepository { ['authorId', 'authorName'] ) }) - const max = await this.prisma - .$queryRaw`SELECT MAX(card_count) as maxCardsCount FROM (SELECT COUNT(*) as card_count FROM card GROUP BY deckId) AS card_counts;` // Return the result with pagination data return { @@ -202,7 +210,6 @@ export class DecksRepository { itemsPerPage, totalPages: Math.ceil(total / itemsPerPage), }, - maxCardsCount: Number(max[0].maxCardsCount), } } catch (e) { this.logger.error(e?.message) diff --git a/src/modules/decks/use-cases/get-all-decks-use-case-v1.ts b/src/modules/decks/use-cases/get-all-decks-use-case-v1.ts new file mode 100644 index 0000000..255718e --- /dev/null +++ b/src/modules/decks/use-cases/get-all-decks-use-case-v1.ts @@ -0,0 +1,21 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs' + +import { GetAllDecksDto } from '../dto' +import { PaginatedDecksWithMaxCardsCount } from '../entities/deck.entity' +import { DecksRepository } from '../infrastructure/decks.repository' + +export class GetAllDecksV1Command { + constructor(public readonly params: GetAllDecksDto) {} +} + +@CommandHandler(GetAllDecksV1Command) +export class GetAllDecksV1Handler implements ICommandHandler { + constructor(private readonly deckRepository: DecksRepository) {} + + async execute(command: GetAllDecksV1Command): Promise { + const decks = await this.deckRepository.findAllDecks(command.params) + const minMax = await this.deckRepository.findMinMaxCards() + + return { ...decks, maxCardsCount: minMax.max } + } +} diff --git a/src/modules/decks/use-cases/get-all-decks-use-case.ts b/src/modules/decks/use-cases/get-all-decks-use-case-v2.ts similarity index 64% rename from src/modules/decks/use-cases/get-all-decks-use-case.ts rename to src/modules/decks/use-cases/get-all-decks-use-case-v2.ts index abcb07a..4ebb95c 100644 --- a/src/modules/decks/use-cases/get-all-decks-use-case.ts +++ b/src/modules/decks/use-cases/get-all-decks-use-case-v2.ts @@ -4,15 +4,15 @@ import { GetAllDecksDto } from '../dto' import { PaginatedDecks } from '../entities/deck.entity' import { DecksRepository } from '../infrastructure/decks.repository' -export class GetAllDecksCommand { +export class GetAllDecksV2Command { constructor(public readonly params: GetAllDecksDto) {} } -@CommandHandler(GetAllDecksCommand) -export class GetAllDecksHandler implements ICommandHandler { +@CommandHandler(GetAllDecksV2Command) +export class GetAllDecksV2Handler implements ICommandHandler { constructor(private readonly deckRepository: DecksRepository) {} - async execute(command: GetAllDecksCommand): Promise { + async execute(command: GetAllDecksV2Command): Promise { return await this.deckRepository.findAllDecks(command.params) } } diff --git a/src/modules/decks/use-cases/get-min-max-cards-use-case.ts b/src/modules/decks/use-cases/get-min-max-cards-use-case.ts new file mode 100644 index 0000000..6756dd1 --- /dev/null +++ b/src/modules/decks/use-cases/get-min-max-cards-use-case.ts @@ -0,0 +1,17 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs' + +import { MinMaxCards } from '../entities/min-max-cards.entity' +import { DecksRepository } from '../infrastructure/decks.repository' + +export class GetMinMaxCardsUseCaseCommand { + constructor() {} +} + +@CommandHandler(GetMinMaxCardsUseCaseCommand) +export class GetMinMaxCardsUseCaseHandler implements ICommandHandler { + constructor(private readonly deckRepository: DecksRepository) {} + + async execute(): Promise { + return await this.deckRepository.findMinMaxCards() + } +} diff --git a/src/modules/decks/use-cases/index.ts b/src/modules/decks/use-cases/index.ts index 2e47db0..060d46c 100644 --- a/src/modules/decks/use-cases/index.ts +++ b/src/modules/decks/use-cases/index.ts @@ -2,8 +2,10 @@ export * from './create-card-use-case' export * from './create-deck-use-case' export * from './delete-deck-by-id-use-case' export * from './get-all-cards-in-deck-use-case' -export * from './get-all-decks-use-case' +export * from './get-all-decks-use-case-v1' +export * from './get-all-decks-use-case-v2' export * from './get-deck-by-id-use-case' export * from './get-random-card-in-deck-use-case' export * from './save-grade-use-case' export * from './update-deck-use-case' +export * from './get-min-max-cards-use-case'