mirror of
https://github.com/ershisan99/flashcards-api.git
synced 2025-12-17 12:33:22 +00:00
add duplicating random card prevention
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
src/modules/decks/dto/get-random-card.dto.ts
Normal file
6
src/modules/decks/dto/get-random-card.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { IsOptionalOrEmptyString } from '../../../infrastructure/decorators'
|
||||||
|
|
||||||
|
export class GetRandomCardDto {
|
||||||
|
@IsOptionalOrEmptyString()
|
||||||
|
previousCardId?: string
|
||||||
|
}
|
||||||
@@ -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',
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user