feat: add to favorites

This commit is contained in:
2024-05-30 20:33:17 +02:00
parent 6604b880bf
commit aba09f5367
6 changed files with 117 additions and 53 deletions

View File

@@ -50,6 +50,7 @@ import {
GetDeckByIdCommand, GetDeckByIdCommand,
GetMinMaxCardsUseCaseCommand, GetMinMaxCardsUseCaseCommand,
GetRandomCardInDeckCommand, GetRandomCardInDeckCommand,
RemoveDeckFromFavoritesCommand,
SaveGradeCommand, SaveGradeCommand,
UpdateDeckCommand, UpdateDeckCommand,
} from './use-cases' } from './use-cases'
@@ -261,4 +262,18 @@ export class DecksController {
async addToFavorites(@Req() req, @Param('id') deckId: string): Promise<CardWithGrade> { async addToFavorites(@Req() req, @Param('id') deckId: string): Promise<CardWithGrade> {
return await this.commandBus.execute(new AddDeckToFavoritesCommand(req.user.id, deckId)) return await this.commandBus.execute(new AddDeckToFavoritesCommand(req.user.id, deckId))
} }
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
@HttpCode(HttpStatus.NO_CONTENT)
@ApiNoContentResponse({ description: 'Added to favorites' })
@Delete(':id/favorite')
@ApiOperation({
description: 'Add deck to favorites',
summary: 'Add deck to favorites',
})
async removeFromFavorites(@Req() req, @Param('id') deckId: string): Promise<CardWithGrade> {
return await this.commandBus.execute(new RemoveDeckFromFavoritesCommand(req.user.id, deckId))
}
} }

View File

@@ -21,6 +21,7 @@ import {
AddDeckToFavoritesHandler, AddDeckToFavoritesHandler,
SaveGradeHandler, SaveGradeHandler,
GetRandomCardInDeckHandler, GetRandomCardInDeckHandler,
RemoveDeckFromFavoritesHandler,
} from './use-cases' } from './use-cases'
const commandHandlers = [ const commandHandlers = [
@@ -36,6 +37,7 @@ const commandHandlers = [
CreateCardHandler, CreateCardHandler,
SaveGradeHandler, SaveGradeHandler,
AddDeckToFavoritesHandler, AddDeckToFavoritesHandler,
RemoveDeckFromFavoritesHandler,
] ]
@Module({ @Module({

View File

@@ -133,34 +133,38 @@ export class DecksRepository {
d.*, d.*,
COUNT(c.id) AS "cardsCount", COUNT(c.id) AS "cardsCount",
a."id" AS "authorId", a."id" AS "authorId",
a."name" AS "authorName" a."name" AS "authorName",
(fd."userId" IS NOT NULL) AS "isFavorite"
FROM flashcards.deck AS "d" FROM flashcards.deck AS "d"
LEFT JOIN "flashcards"."card" AS c ON d."id" = c."deckId" LEFT JOIN "flashcards"."card" AS c ON d."id" = c."deckId"
LEFT JOIN "flashcards"."user" AS a ON d."userId" = a.id LEFT JOIN "flashcards"."user" AS a ON d."userId" = a.id
LEFT JOIN "flashcards"."favoriteDeck" AS fd ON d."id" = fd."deckId" LEFT JOIN "flashcards"."favoriteDeck" AS fd ON d."id" = fd."deckId" AND fd."userId" = $1
${ ${
conditions.length conditions.length
? `WHERE ${conditions ? `WHERE ${conditions
.map((_, index) => `${_.replace('?', `$${index + 1}`)}`) .map((condition, index) => `${condition.replace('?', `$${index + 2}`)}`)
.join(' AND ')}` .join(' AND ')}`
: '' : ''
} }
GROUP BY d."id", a."id" GROUP BY d."id", a."id", fd."userId"
${ ${
havingConditions.length havingConditions.length
? `HAVING ${havingConditions ? `HAVING ${havingConditions
.map((_, index) => `${_.replace('?', `$${conditions.length + index + 1}`)}`) .map(
(condition, index) => `${condition.replace('?', `$${conditions.length + index + 2}`)}`
)
.join(' AND ')}` .join(' AND ')}`
: '' : ''
} }
ORDER BY ${orderField} ${orderDirection} ORDER BY ${orderField} ${orderDirection}
LIMIT $${conditions.length + havingConditions.length + 1} OFFSET $${ LIMIT $${conditions.length + havingConditions.length + 2} OFFSET $${
conditions.length + havingConditions.length + 2 conditions.length + havingConditions.length + 3
}; };
` `
// Parameters for fetching decks // Parameters for fetching decks
const deckQueryParams = [ const deckQueryParams = [
userId,
...(name ? [name] : []), ...(name ? [name] : []),
...(authorId ? [authorId] : []), ...(authorId ? [authorId] : []),
...(userId ? [userId] : []), ...(userId ? [userId] : []),
@@ -177,6 +181,7 @@ export class DecksRepository {
Deck & { Deck & {
authorId: string authorId: string
authorName: string authorName: string
isFavorite: boolean
} }
> >
>(query, ...deckQueryParams) >(query, ...deckQueryParams)
@@ -188,19 +193,22 @@ export class DecksRepository {
SELECT d.id SELECT d.id
FROM flashcards.deck AS d FROM flashcards.deck AS d
LEFT JOIN flashcards.card AS c ON d.id = c."deckId" LEFT JOIN flashcards.card AS c ON d.id = c."deckId"
LEFT JOIN flashcards."favoriteDeck" AS fd ON d."id" = fd."deckId" LEFT JOIN flashcards."favoriteDeck" AS fd ON d."id" = fd."deckId" AND fd."userId" = $1
${ ${
conditions.length conditions.length
? `WHERE ${conditions ? `WHERE ${conditions
.map((_, index) => `${_.replace('?', `$${index + 1}`)}`) .map((condition, index) => `${condition.replace('?', `$${index + 2}`)}`)
.join(' AND ')}` .join(' AND ')}`
: '' : ''
} }
GROUP BY d.id GROUP BY d.id, fd."userId"
${ ${
havingConditions.length havingConditions.length
? `HAVING ${havingConditions ? `HAVING ${havingConditions
.map((_, index) => `${_.replace('?', `$${conditions.length + index + 1}`)}`) .map(
(condition, index) =>
`${condition.replace('?', `$${conditions.length + index + 2}`)}`
)
.join(' AND ')}` .join(' AND ')}`
: '' : ''
} }
@@ -209,6 +217,7 @@ export class DecksRepository {
// Parameters for total count query // Parameters for total count query
const countQueryParams = [ const countQueryParams = [
userId,
...(name ? [name] : []), ...(name ? [name] : []),
...(authorId ? [authorId] : []), ...(authorId ? [authorId] : []),
...(userId ? [userId] : []), ...(userId ? [userId] : []),
@@ -220,6 +229,7 @@ export class DecksRepository {
// Execute the raw SQL query for total count // Execute the raw SQL query for total count
const totalResult = await this.prisma.$queryRawUnsafe<any[]>(countQuery, ...countQueryParams) const totalResult = await this.prisma.$queryRawUnsafe<any[]>(countQuery, ...countQueryParams)
const total = Number(totalResult[0]?.total) ?? 1 const total = Number(totalResult[0]?.total) ?? 1
const modifiedDecks = decks.map(deck => { const modifiedDecks = decks.map(deck => {
const cardsCount = deck.cardsCount const cardsCount = deck.cardsCount

View File

@@ -9,4 +9,5 @@ export * from './get-random-card-in-deck-use-case'
export * from './save-grade-use-case' export * from './save-grade-use-case'
export * from './update-deck-use-case' export * from './update-deck-use-case'
export * from './get-min-max-cards-use-case' export * from './get-min-max-cards-use-case'
export * from './add-card-to-favorites.use-case' export * from './add-deck-to-favorites.use-case'
export * from './remove-deck-from-favorites.use-case'

View File

@@ -0,0 +1,36 @@
import { BadRequestException, NotFoundException } from '@nestjs/common'
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
import { DecksRepository } from '../infrastructure/decks.repository'
export class RemoveDeckFromFavoritesCommand {
constructor(
public readonly userId: string,
public readonly deckId: string
) {}
}
@CommandHandler(RemoveDeckFromFavoritesCommand)
export class RemoveDeckFromFavoritesHandler
implements ICommandHandler<RemoveDeckFromFavoritesCommand>
{
constructor(private readonly decksRepository: DecksRepository) {}
async execute(command: RemoveDeckFromFavoritesCommand): Promise<void> {
const deck = await this.decksRepository.findDeckById(command.deckId)
if (!deck) {
throw new NotFoundException(`Deck with id ${command.deckId} not found`)
}
const favorites = await this.decksRepository.findFavoritesByUserId(
command.userId,
command.deckId
)
if (!favorites?.includes(command.deckId)) {
throw new BadRequestException(`Deck with id ${command.deckId} is not a favorite`)
}
return await this.decksRepository.removeDeckFromFavorites(command.userId, command.deckId)
}
}