mirror of
https://github.com/ershisan99/flashcards-api.git
synced 2025-12-17 12:33:22 +00:00
feat: add to favorites
This commit is contained in:
@@ -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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -129,38 +129,42 @@ export class DecksRepository {
|
|||||||
|
|
||||||
// Construct the raw SQL query for fetching decks
|
// Construct the raw SQL query for fetching decks
|
||||||
const query = `
|
const query = `
|
||||||
SELECT
|
SELECT
|
||||||
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",
|
||||||
FROM flashcards.deck AS "d"
|
(fd."userId" IS NOT NULL) AS "isFavorite"
|
||||||
LEFT JOIN "flashcards"."card" AS c ON d."id" = c."deckId"
|
FROM flashcards.deck AS "d"
|
||||||
LEFT JOIN "flashcards"."user" AS a ON d."userId" = a.id
|
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"."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
|
conditions.length
|
||||||
.map((_, index) => `${_.replace('?', `$${index + 1}`)}`)
|
? `WHERE ${conditions
|
||||||
.join(' AND ')}`
|
.map((condition, index) => `${condition.replace('?', `$${index + 2}`)}`)
|
||||||
: ''
|
.join(' AND ')}`
|
||||||
}
|
: ''
|
||||||
GROUP BY d."id", a."id"
|
}
|
||||||
${
|
GROUP BY d."id", a."id", fd."userId"
|
||||||
havingConditions.length
|
${
|
||||||
? `HAVING ${havingConditions
|
havingConditions.length
|
||||||
.map((_, index) => `${_.replace('?', `$${conditions.length + index + 1}`)}`)
|
? `HAVING ${havingConditions
|
||||||
.join(' AND ')}`
|
.map(
|
||||||
: ''
|
(condition, index) => `${condition.replace('?', `$${conditions.length + index + 2}`)}`
|
||||||
}
|
)
|
||||||
ORDER BY ${orderField} ${orderDirection}
|
.join(' AND ')}`
|
||||||
LIMIT $${conditions.length + havingConditions.length + 1} OFFSET $${
|
: ''
|
||||||
conditions.length + havingConditions.length + 2
|
}
|
||||||
};
|
ORDER BY ${orderField} ${orderDirection}
|
||||||
`
|
LIMIT $${conditions.length + havingConditions.length + 2} OFFSET $${
|
||||||
|
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,38 +181,43 @@ export class DecksRepository {
|
|||||||
Deck & {
|
Deck & {
|
||||||
authorId: string
|
authorId: string
|
||||||
authorName: string
|
authorName: string
|
||||||
|
isFavorite: boolean
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
>(query, ...deckQueryParams)
|
>(query, ...deckQueryParams)
|
||||||
|
|
||||||
// Construct the raw SQL query for total count
|
// Construct the raw SQL query for total count
|
||||||
const countQuery = `
|
const countQuery = `
|
||||||
SELECT COUNT(*) AS total
|
SELECT COUNT(*) AS total
|
||||||
FROM (
|
FROM (
|
||||||
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(
|
||||||
.join(' AND ')}`
|
(condition, index) =>
|
||||||
: ''
|
`${condition.replace('?', `$${conditions.length + index + 2}`)}`
|
||||||
}
|
)
|
||||||
) AS subquery;
|
.join(' AND ')}`
|
||||||
`
|
: ''
|
||||||
|
}
|
||||||
|
) AS subquery;
|
||||||
|
`
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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