feat: add get /decks v2 which does not return maxCardsCount. Add /decks/min-max-cards to use instead. /v1/decks is now deprecated

This commit is contained in:
2024-01-21 15:52:03 +01:00
parent e0b2704888
commit 32c8df6a4c
10 changed files with 114 additions and 18 deletions

View File

@@ -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')

View File

@@ -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<PaginatedDecks> {
findAllV1(@Query() query: GetAllDecksDto, @Req() req): Promise<PaginatedDecksWithMaxCardsCount> {
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<PaginatedDecksWithMaxCardsCount> {
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<MinMaxCards> {
return this.commandBus.execute(new GetMinMaxCardsUseCaseCommand())
}
@ApiConsumes('multipart/form-data')

View File

@@ -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,

View File

@@ -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
}

View File

@@ -0,0 +1,4 @@
export class MinMaxCards {
max: number
min: number
}

View File

@@ -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)

View File

@@ -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<GetAllDecksV1Command> {
constructor(private readonly deckRepository: DecksRepository) {}
async execute(command: GetAllDecksV1Command): Promise<PaginatedDecksWithMaxCardsCount> {
const decks = await this.deckRepository.findAllDecks(command.params)
const minMax = await this.deckRepository.findMinMaxCards()
return { ...decks, maxCardsCount: minMax.max }
}
}

View File

@@ -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<GetAllDecksCommand> {
@CommandHandler(GetAllDecksV2Command)
export class GetAllDecksV2Handler implements ICommandHandler<GetAllDecksV2Command> {
constructor(private readonly deckRepository: DecksRepository) {}
async execute(command: GetAllDecksCommand): Promise<PaginatedDecks> {
async execute(command: GetAllDecksV2Command): Promise<PaginatedDecks> {
return await this.deckRepository.findAllDecks(command.params)
}
}

View File

@@ -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<GetMinMaxCardsUseCaseCommand> {
constructor(private readonly deckRepository: DecksRepository) {}
async execute(): Promise<MinMaxCards> {
return await this.deckRepository.findMinMaxCards()
}
}

View File

@@ -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'