mirror of
https://github.com/ershisan99/flashcards-api.git
synced 2025-12-16 20:59:26 +00:00
feat: add to favorites
This commit is contained in:
@@ -50,6 +50,7 @@ import {
|
||||
GetDeckByIdCommand,
|
||||
GetMinMaxCardsUseCaseCommand,
|
||||
GetRandomCardInDeckCommand,
|
||||
RemoveDeckFromFavoritesCommand,
|
||||
SaveGradeCommand,
|
||||
UpdateDeckCommand,
|
||||
} from './use-cases'
|
||||
@@ -261,4 +262,18 @@ export class DecksController {
|
||||
async addToFavorites(@Req() req, @Param('id') deckId: string): Promise<CardWithGrade> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
AddDeckToFavoritesHandler,
|
||||
SaveGradeHandler,
|
||||
GetRandomCardInDeckHandler,
|
||||
RemoveDeckFromFavoritesHandler,
|
||||
} from './use-cases'
|
||||
|
||||
const commandHandlers = [
|
||||
@@ -36,6 +37,7 @@ const commandHandlers = [
|
||||
CreateCardHandler,
|
||||
SaveGradeHandler,
|
||||
AddDeckToFavoritesHandler,
|
||||
RemoveDeckFromFavoritesHandler,
|
||||
]
|
||||
|
||||
@Module({
|
||||
|
||||
@@ -129,38 +129,42 @@ export class DecksRepository {
|
||||
|
||||
// Construct the raw SQL query for fetching decks
|
||||
const query = `
|
||||
SELECT
|
||||
d.*,
|
||||
COUNT(c.id) AS "cardsCount",
|
||||
a."id" AS "authorId",
|
||||
a."name" AS "authorName"
|
||||
FROM flashcards.deck AS "d"
|
||||
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"."favoriteDeck" AS fd ON d."id" = fd."deckId"
|
||||
${
|
||||
conditions.length
|
||||
? `WHERE ${conditions
|
||||
.map((_, index) => `${_.replace('?', `$${index + 1}`)}`)
|
||||
.join(' AND ')}`
|
||||
: ''
|
||||
}
|
||||
GROUP BY d."id", a."id"
|
||||
${
|
||||
havingConditions.length
|
||||
? `HAVING ${havingConditions
|
||||
.map((_, index) => `${_.replace('?', `$${conditions.length + index + 1}`)}`)
|
||||
.join(' AND ')}`
|
||||
: ''
|
||||
}
|
||||
ORDER BY ${orderField} ${orderDirection}
|
||||
LIMIT $${conditions.length + havingConditions.length + 1} OFFSET $${
|
||||
conditions.length + havingConditions.length + 2
|
||||
};
|
||||
`
|
||||
SELECT
|
||||
d.*,
|
||||
COUNT(c.id) AS "cardsCount",
|
||||
a."id" AS "authorId",
|
||||
a."name" AS "authorName",
|
||||
(fd."userId" IS NOT NULL) AS "isFavorite"
|
||||
FROM flashcards.deck AS "d"
|
||||
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"."favoriteDeck" AS fd ON d."id" = fd."deckId" AND fd."userId" = $1
|
||||
${
|
||||
conditions.length
|
||||
? `WHERE ${conditions
|
||||
.map((condition, index) => `${condition.replace('?', `$${index + 2}`)}`)
|
||||
.join(' AND ')}`
|
||||
: ''
|
||||
}
|
||||
GROUP BY d."id", a."id", fd."userId"
|
||||
${
|
||||
havingConditions.length
|
||||
? `HAVING ${havingConditions
|
||||
.map(
|
||||
(condition, index) => `${condition.replace('?', `$${conditions.length + index + 2}`)}`
|
||||
)
|
||||
.join(' AND ')}`
|
||||
: ''
|
||||
}
|
||||
ORDER BY ${orderField} ${orderDirection}
|
||||
LIMIT $${conditions.length + havingConditions.length + 2} OFFSET $${
|
||||
conditions.length + havingConditions.length + 3
|
||||
};
|
||||
`
|
||||
|
||||
// Parameters for fetching decks
|
||||
const deckQueryParams = [
|
||||
userId,
|
||||
...(name ? [name] : []),
|
||||
...(authorId ? [authorId] : []),
|
||||
...(userId ? [userId] : []),
|
||||
@@ -177,38 +181,43 @@ export class DecksRepository {
|
||||
Deck & {
|
||||
authorId: string
|
||||
authorName: string
|
||||
isFavorite: boolean
|
||||
}
|
||||
>
|
||||
>(query, ...deckQueryParams)
|
||||
|
||||
// Construct the raw SQL query for total count
|
||||
const countQuery = `
|
||||
SELECT COUNT(*) AS total
|
||||
FROM (
|
||||
SELECT d.id
|
||||
FROM flashcards.deck AS d
|
||||
LEFT JOIN flashcards.card AS c ON d.id = c."deckId"
|
||||
LEFT JOIN flashcards."favoriteDeck" AS fd ON d."id" = fd."deckId"
|
||||
${
|
||||
conditions.length
|
||||
? `WHERE ${conditions
|
||||
.map((_, index) => `${_.replace('?', `$${index + 1}`)}`)
|
||||
.join(' AND ')}`
|
||||
: ''
|
||||
}
|
||||
GROUP BY d.id
|
||||
${
|
||||
havingConditions.length
|
||||
? `HAVING ${havingConditions
|
||||
.map((_, index) => `${_.replace('?', `$${conditions.length + index + 1}`)}`)
|
||||
.join(' AND ')}`
|
||||
: ''
|
||||
}
|
||||
) AS subquery;
|
||||
`
|
||||
SELECT COUNT(*) AS total
|
||||
FROM (
|
||||
SELECT d.id
|
||||
FROM flashcards.deck AS d
|
||||
LEFT JOIN flashcards.card AS c ON d.id = c."deckId"
|
||||
LEFT JOIN flashcards."favoriteDeck" AS fd ON d."id" = fd."deckId" AND fd."userId" = $1
|
||||
${
|
||||
conditions.length
|
||||
? `WHERE ${conditions
|
||||
.map((condition, index) => `${condition.replace('?', `$${index + 2}`)}`)
|
||||
.join(' AND ')}`
|
||||
: ''
|
||||
}
|
||||
GROUP BY d.id, fd."userId"
|
||||
${
|
||||
havingConditions.length
|
||||
? `HAVING ${havingConditions
|
||||
.map(
|
||||
(condition, index) =>
|
||||
`${condition.replace('?', `$${conditions.length + index + 2}`)}`
|
||||
)
|
||||
.join(' AND ')}`
|
||||
: ''
|
||||
}
|
||||
) AS subquery;
|
||||
`
|
||||
|
||||
// Parameters for total count query
|
||||
const countQueryParams = [
|
||||
userId,
|
||||
...(name ? [name] : []),
|
||||
...(authorId ? [authorId] : []),
|
||||
...(userId ? [userId] : []),
|
||||
@@ -220,6 +229,7 @@ export class DecksRepository {
|
||||
// Execute the raw SQL query for total count
|
||||
const totalResult = await this.prisma.$queryRawUnsafe<any[]>(countQuery, ...countQueryParams)
|
||||
const total = Number(totalResult[0]?.total) ?? 1
|
||||
|
||||
const modifiedDecks = decks.map(deck => {
|
||||
const cardsCount = deck.cardsCount
|
||||
|
||||
|
||||
@@ -9,4 +9,5 @@ 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'
|
||||
export * from './add-card-to-favorites.use-case'
|
||||
export * from './add-deck-to-favorites.use-case'
|
||||
export * from './remove-deck-from-favorites.use-case'
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user