add duplicating random card prevention

This commit is contained in:
2023-08-07 14:13:07 +02:00
parent 663a93cbb6
commit fff0288659
5 changed files with 46 additions and 15 deletions

View File

@@ -44,7 +44,6 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
if (req.cookies && 'accessToken' in req.cookies && req.cookies.accessToken.length > 0) { if (req.cookies && 'accessToken' in req.cookies && req.cookies.accessToken.length > 0) {
return req.cookies.accessToken return req.cookies.accessToken
} }
console.log(req.headers)
return null return null
} }

View File

@@ -34,18 +34,19 @@ import { CreateCardDto, GetAllCardsInDeckDto } from '../cards/dto'
import { Card, PaginatedCards } from '../cards/entities/cards.entity' import { Card, PaginatedCards } from '../cards/entities/cards.entity'
import { DecksService } from './decks.service' import { DecksService } from './decks.service'
import { UpdateDeckDto, CreateDeckDto, GetAllDecksDto } from './dto' 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, PaginatedDecks } from './entities/deck.entity'
import { import {
CreateCardCommand,
CreateDeckCommand, CreateDeckCommand,
DeleteDeckByIdCommand, DeleteDeckByIdCommand,
GetAllCardsInDeckCommand, GetAllCardsInDeckCommand,
GetAllDecksCommand, GetAllDecksCommand,
GetDeckByIdCommand, GetDeckByIdCommand,
UpdateDeckCommand,
GetRandomCardInDeckCommand, GetRandomCardInDeckCommand,
SaveGradeCommand, SaveGradeCommand,
CreateCardCommand, UpdateDeckCommand,
} from './use-cases' } from './use-cases'
@ApiTags('Decks') @ApiTags('Decks')
@@ -172,23 +173,33 @@ export class DecksController {
summary: 'Retrieve a random card', summary: 'Retrieve a random card',
}) })
@Get(':id/learn') @Get(':id/learn')
findRandomCardInDeck(@Param('id') id: string, @Req() req): Promise<Card> { findRandomCardInDeck(
return this.commandBus.execute(new GetRandomCardInDeckCommand(req.user.id, id)) @Param('id') id: string,
@Req() req,
@Query() query: GetRandomCardDto
): Promise<Card> {
return this.commandBus.execute(
new GetRandomCardInDeckCommand(req.user.id, id, query.previousCardId)
)
} }
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiUnauthorizedResponse({ description: 'Unauthorized' }) @ApiUnauthorizedResponse({ description: 'Unauthorized' })
@ApiNotFoundResponse({ description: 'Card not found' }) @ApiNotFoundResponse({ description: 'Card not found' })
@HttpCode(HttpStatus.NO_CONTENT) @HttpCode(HttpStatus.OK)
@ApiNoContentResponse({ description: 'Grade saved' }) @ApiNoContentResponse({ description: 'Grade saved' })
@Post(':id/learn') @Post(':id/learn')
@ApiOperation({ @ApiOperation({
description: 'Save the grade of a card', description: 'Save the grade of a card',
summary: 'Save the grade of a card', summary: 'Save the grade of a card',
}) })
saveGrade(@Param('id') id: string, @Req() req, @Body() body: SaveGradeDto): Promise<void> { async saveGrade(@Param('id') id: string, @Req() req, @Body() body: SaveGradeDto) {
return this.commandBus.execute( const saved = await this.commandBus.execute(
new SaveGradeCommand(req.user.id, { cardId: body.cardId, grade: body.grade }) new SaveGradeCommand(req.user.id, { cardId: body.cardId, grade: body.grade })
) )
return await this.commandBus.execute(
new GetRandomCardInDeckCommand(req.user.id, saved.deckId, saved.id)
)
} }
} }

View File

@@ -0,0 +1,6 @@
import { IsOptionalOrEmptyString } from '../../../infrastructure/decorators'
export class GetRandomCardDto {
@IsOptionalOrEmptyString()
previousCardId?: string
}

View File

@@ -8,7 +8,11 @@ import { CardsRepository } from '../../cards/infrastructure/cards.repository'
import { DecksRepository } from '../infrastructure/decks.repository' import { DecksRepository } from '../infrastructure/decks.repository'
export class GetRandomCardInDeckCommand { export class GetRandomCardInDeckCommand {
constructor(public readonly userId: string, public readonly deckId: string) {} constructor(
public readonly userId: string,
public readonly deckId: string,
public readonly previousCardId: string
) {}
} }
type CardWithGrade = Prisma.cardGetPayload<{ include: { grades: true } }> type CardWithGrade = Prisma.cardGetPayload<{ include: { grades: true } }>
@@ -41,6 +45,19 @@ export class GetRandomCardInDeckHandler implements ICommandHandler<GetRandomCard
return selectionPool[Math.floor(Math.random() * selectionPool.length)] return selectionPool[Math.floor(Math.random() * selectionPool.length)]
} }
private async getNotDuplicateRandomCard(
cards: Array<CardWithGrade>,
previousCardId: string
): Promise<Card> {
const randomCard = await this.getSmartRandomCard(cards)
if (randomCard.id === previousCardId) {
return this.getNotDuplicateRandomCard(cards, previousCardId)
}
return randomCard
}
async execute(command: GetRandomCardInDeckCommand) { async execute(command: GetRandomCardInDeckCommand) {
const deck = await this.decksRepository.findDeckById(command.deckId) const deck = await this.decksRepository.findDeckById(command.deckId)
@@ -54,7 +71,7 @@ export class GetRandomCardInDeckHandler implements ICommandHandler<GetRandomCard
command.userId, command.userId,
command.deckId command.deckId
) )
const smartRandomCard = await this.getSmartRandomCard(cards) const smartRandomCard = await this.getNotDuplicateRandomCard(cards, command.previousCardId)
return pick(smartRandomCard, [ return pick(smartRandomCard, [
'id', 'id',

View File

@@ -1,7 +1,6 @@
import { ForbiddenException, NotFoundException } from '@nestjs/common' import { ForbiddenException, NotFoundException } from '@nestjs/common'
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs' import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
import { CardsRepository } from '../../cards/infrastructure/cards.repository'
import { DecksRepository } from '../infrastructure/decks.repository' import { DecksRepository } from '../infrastructure/decks.repository'
import { GradesRepository } from '../infrastructure/grades.repository' import { GradesRepository } from '../infrastructure/grades.repository'
@@ -18,12 +17,11 @@ export class SaveGradeCommand {
@CommandHandler(SaveGradeCommand) @CommandHandler(SaveGradeCommand)
export class SaveGradeHandler implements ICommandHandler<SaveGradeCommand> { export class SaveGradeHandler implements ICommandHandler<SaveGradeCommand> {
constructor( constructor(
private readonly cardsRepository: CardsRepository,
private readonly decksRepository: DecksRepository, private readonly decksRepository: DecksRepository,
private readonly gradesRepository: GradesRepository private readonly gradesRepository: GradesRepository
) {} ) {}
async execute(command: SaveGradeCommand): Promise<void> { async execute(command: SaveGradeCommand) {
const deck = await this.decksRepository.findDeckByCardId(command.args.cardId) const deck = await this.decksRepository.findDeckByCardId(command.args.cardId)
if (!deck) if (!deck)
@@ -33,7 +31,7 @@ export class SaveGradeHandler implements ICommandHandler<SaveGradeCommand> {
throw new ForbiddenException(`You can't save cards to a private deck that you don't own`) throw new ForbiddenException(`You can't save cards to a private deck that you don't own`)
} }
await this.gradesRepository.createGrade({ return await this.gradesRepository.createGrade({
userId: command.userId, userId: command.userId,
grade: command.args.grade, grade: command.args.grade,
cardId: command.args.cardId, cardId: command.args.cardId,