add decks documentation

This commit is contained in:
2023-07-16 23:23:46 +02:00
parent 971b165be8
commit 9c13a57804
21 changed files with 239 additions and 78 deletions

View File

@@ -12,3 +12,10 @@ export class PaginationDto {
@IsNumber() @IsNumber()
itemsPerPage?: number itemsPerPage?: number
} }
export class Pagination {
currentPage: number
itemsPerPage: number
totalPages: number
totalItems: number
}

View File

@@ -19,7 +19,7 @@ async function bootstrap() {
app.setGlobalPrefix('v1') app.setGlobalPrefix('v1')
const config = new DocumentBuilder() const config = new DocumentBuilder()
.setTitle('Flashcards') .setTitle('Flashcards')
.setDescription('The config API description') .setDescription('Flashcards API')
.setVersion('1.0') .setVersion('1.0')
.addTag('Auth') .addTag('Auth')
.addTag('Decks') .addTag('Decks')

View File

@@ -52,7 +52,7 @@ import {
export class AuthController { export class AuthController {
constructor(private commandBus: CommandBus) {} constructor(private commandBus: CommandBus) {}
@ApiOperation({ description: 'Retrieve current user data.' }) @ApiOperation({ description: 'Retrieve current user data.', summary: 'Current user data' })
@ApiUnauthorizedResponse({ description: 'Not logged in' }) @ApiUnauthorizedResponse({ description: 'Not logged in' })
@ApiBadRequestResponse({ description: 'User not found' }) @ApiBadRequestResponse({ description: 'User not found' })
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@@ -63,7 +63,10 @@ export class AuthController {
return await this.commandBus.execute(new GetCurrentUserDataCommand(userId)) return await this.commandBus.execute(new GetCurrentUserDataCommand(userId))
} }
@ApiOperation({ description: 'Sign in using email and password. Must have an account to do so.' }) @ApiOperation({
description: 'Sign in using email and password. Must have an account to do so.',
summary: 'Sign in using email and password. Must have an account to do so.',
})
@ApiUnauthorizedResponse({ description: 'Invalid credentials' }) @ApiUnauthorizedResponse({ description: 'Invalid credentials' })
@ApiBody({ type: LoginDto }) @ApiBody({ type: LoginDto })
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@@ -90,7 +93,7 @@ export class AuthController {
return { accessToken: req.user.data.accessToken } return { accessToken: req.user.data.accessToken }
} }
@ApiOperation({ description: 'Create a new user account' }) @ApiOperation({ description: 'Create a new user account', summary: 'Create a new user account' })
@ApiBadRequestResponse({ description: 'Email already exists' }) @ApiBadRequestResponse({ description: 'Email already exists' })
@HttpCode(HttpStatus.CREATED) @HttpCode(HttpStatus.CREATED)
@Post('sign-up') @Post('sign-up')
@@ -98,7 +101,7 @@ export class AuthController {
return await this.commandBus.execute(new CreateUserCommand(registrationData)) return await this.commandBus.execute(new CreateUserCommand(registrationData))
} }
@ApiOperation({ description: 'Verify user email' }) @ApiOperation({ description: 'Verify user email', summary: 'Verify user email' })
@ApiBadRequestResponse({ description: 'Email has already been verified' }) @ApiBadRequestResponse({ description: 'Email has already been verified' })
@ApiNotFoundResponse({ description: 'User not found' }) @ApiNotFoundResponse({ description: 'User not found' })
@ApiNoContentResponse({ description: 'Email verified successfully' }) @ApiNoContentResponse({ description: 'Email verified successfully' })
@@ -108,7 +111,10 @@ export class AuthController {
return await this.commandBus.execute(new VerifyEmailCommand(body.code)) return await this.commandBus.execute(new VerifyEmailCommand(body.code))
} }
@ApiOperation({ description: 'Send verification email again' }) @ApiOperation({
description: 'Send verification email again',
summary: 'Send verification email again',
})
@ApiBadRequestResponse({ description: 'Email has already been verified' }) @ApiBadRequestResponse({ description: 'Email has already been verified' })
@ApiNotFoundResponse({ description: 'User not found' }) @ApiNotFoundResponse({ description: 'User not found' })
@ApiNoContentResponse({ description: 'Verification email sent successfully' }) @ApiNoContentResponse({ description: 'Verification email sent successfully' })
@@ -118,7 +124,7 @@ export class AuthController {
return await this.commandBus.execute(new ResendVerificationEmailCommand(body.userId)) return await this.commandBus.execute(new ResendVerificationEmailCommand(body.userId))
} }
@ApiOperation({ description: 'Sign current user out' }) @ApiOperation({ description: 'Sign current user out', summary: 'Sign current user out' })
@ApiUnauthorizedResponse({ description: 'Not logged in' }) @ApiUnauthorizedResponse({ description: 'Not logged in' })
@ApiNoContentResponse({ description: 'Logged out successfully' }) @ApiNoContentResponse({ description: 'Logged out successfully' })
@HttpCode(HttpStatus.NO_CONTENT) @HttpCode(HttpStatus.NO_CONTENT)
@@ -136,7 +142,10 @@ export class AuthController {
return null return null
} }
@ApiOperation({ description: 'Get new access token using refresh token' }) @ApiOperation({
description: 'Get new access token using refresh token',
summary: 'Get new access token using refresh token',
})
@ApiUnauthorizedResponse({ description: 'Invalid or missing refreshToken' }) @ApiUnauthorizedResponse({ description: 'Invalid or missing refreshToken' })
@ApiNoContentResponse({ description: 'New tokens generated successfully' }) @ApiNoContentResponse({ description: 'New tokens generated successfully' })
@HttpCode(HttpStatus.NO_CONTENT) @HttpCode(HttpStatus.NO_CONTENT)
@@ -165,7 +174,10 @@ export class AuthController {
return null return null
} }
@ApiOperation({ description: 'Send password recovery email' }) @ApiOperation({
description: 'Send password recovery email',
summary: 'Send password recovery email',
})
@ApiBadRequestResponse({ description: 'Email has already been verified' }) @ApiBadRequestResponse({ description: 'Email has already been verified' })
@ApiNotFoundResponse({ description: 'User not found' }) @ApiNotFoundResponse({ description: 'User not found' })
@ApiNoContentResponse({ description: 'Password recovery email sent successfully' }) @ApiNoContentResponse({ description: 'Password recovery email sent successfully' })
@@ -175,7 +187,7 @@ export class AuthController {
return await this.commandBus.execute(new SendPasswordRecoveryEmailCommand(body.email)) return await this.commandBus.execute(new SendPasswordRecoveryEmailCommand(body.email))
} }
@ApiOperation({ description: 'Reset password' }) @ApiOperation({ description: 'Reset password', summary: 'Reset password' })
@ApiBadRequestResponse({ description: 'Password is required' }) @ApiBadRequestResponse({ description: 'Password is required' })
@ApiNotFoundResponse({ description: 'Incorrect or expired password reset token' }) @ApiNotFoundResponse({ description: 'Incorrect or expired password reset token' })
@ApiNoContentResponse({ description: 'Password reset successfully' }) @ApiNoContentResponse({ description: 'Password reset successfully' })

View File

@@ -3,4 +3,5 @@ export * from './login.dto'
export * from './recover-password.dto' export * from './recover-password.dto'
export * from './registration.dto' export * from './registration.dto'
export * from './resend-verification-email.dto' export * from './resend-verification-email.dto'
export * from './save-grade.dto'
export * from './update-auth.dto' export * from './update-auth.dto'

View File

@@ -0,0 +1,4 @@
export class SaveGradeDto {
cardId: string
grade: number
}

View File

@@ -1 +1,20 @@
export class Card {} import { Pagination } from '../../../infrastructure/common/pagination/pagination.dto'
export class Card {
id: string
deckId: string
userId: string
question: string
answer: string
shots: number
answerImg: string
questionImg: string
rating: number
created: Date
updated: Date
}
export class PaginatedCards {
items: Card[]
pagination: Pagination
}

View File

@@ -4,6 +4,7 @@ import { createPrismaOrderBy } from '../../../infrastructure/common/helpers/get-
import { Pagination } from '../../../infrastructure/common/pagination/pagination.service' import { Pagination } from '../../../infrastructure/common/pagination/pagination.service'
import { PrismaService } from '../../../prisma.service' import { PrismaService } from '../../../prisma.service'
import { CreateCardDto, GetAllCardsInDeckDto, UpdateCardDto } from '../dto' import { CreateCardDto, GetAllCardsInDeckDto, UpdateCardDto } from '../dto'
import { PaginatedCards } from '../entities/cards.entity'
@Injectable() @Injectable()
export class CardsRepository { export class CardsRepository {
@@ -59,7 +60,7 @@ export class CardsRepository {
itemsPerPage, itemsPerPage,
orderBy, orderBy,
}: GetAllCardsInDeckDto }: GetAllCardsInDeckDto
) { ): Promise<PaginatedCards> {
try { try {
const where = { const where = {
decks: { decks: {

View File

@@ -3,6 +3,8 @@ import {
Controller, Controller,
Delete, Delete,
Get, Get,
HttpCode,
HttpStatus,
Param, Param,
Patch, Patch,
Post, Post,
@@ -15,14 +17,25 @@ import {
} from '@nestjs/common' } from '@nestjs/common'
import { CommandBus } from '@nestjs/cqrs' import { CommandBus } from '@nestjs/cqrs'
import { FileFieldsInterceptor } from '@nestjs/platform-express' import { FileFieldsInterceptor } from '@nestjs/platform-express'
import { ApiTags } from '@nestjs/swagger' import {
ApiConsumes,
ApiNoContentResponse,
ApiNotFoundResponse,
ApiOkResponse,
ApiOperation,
ApiTags,
ApiUnauthorizedResponse,
} from '@nestjs/swagger'
import { Pagination } from '../../infrastructure/common/pagination/pagination.service' import { Pagination } from '../../infrastructure/common/pagination/pagination.service'
import { SaveGradeDto } from '../auth/dto'
import { JwtAuthGuard } from '../auth/guards' import { JwtAuthGuard } from '../auth/guards'
import { CreateCardDto, GetAllCardsInDeckDto } from '../cards/dto' import { CreateCardDto, GetAllCardsInDeckDto } from '../cards/dto'
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 { UpdateDeckDto, CreateDeckDto, GetAllDecksDto } from './dto'
import { Deck, PaginatedDecks } from './entities/deck.entity'
import { import {
CreateDeckCommand, CreateDeckCommand,
DeleteDeckByIdCommand, DeleteDeckByIdCommand,
@@ -40,6 +53,20 @@ import {
export class DecksController { export class DecksController {
constructor(private readonly decksService: DecksService, private commandBus: CommandBus) {} constructor(private readonly decksService: DecksService, private commandBus: CommandBus) {}
@HttpCode(HttpStatus.PARTIAL_CONTENT)
@ApiOperation({ description: 'Retrieve paginated decks list.', summary: 'Paginated decks list' })
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
@UseGuards(JwtAuthGuard)
@Get()
findAll(@Query() query: GetAllDecksDto, @Req() req): Promise<PaginatedDecks> {
const finalQuery = Pagination.getPaginationData(query)
return this.commandBus.execute(new GetAllDecksCommand({ ...finalQuery, userId: req.user.id }))
}
@ApiConsumes('multipart/form-data')
@ApiOperation({ description: 'Create a deck', summary: 'Create a deck' })
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@UseInterceptors(FileFieldsInterceptor([{ name: 'cover', maxCount: 1 }])) @UseInterceptors(FileFieldsInterceptor([{ name: 'cover', maxCount: 1 }]))
@Post() @Post()
@@ -50,7 +77,7 @@ export class DecksController {
cover: Express.Multer.File[] cover: Express.Multer.File[]
}, },
@Body() createDeckDto: CreateDeckDto @Body() createDeckDto: CreateDeckDto
) { ): Promise<Deck> {
const userId = req.user.id const userId = req.user.id
return this.commandBus.execute( return this.commandBus.execute(
@@ -58,42 +85,64 @@ export class DecksController {
) )
} }
@UseGuards(JwtAuthGuard) @ApiOperation({ description: 'Retrieve a deck by id', summary: 'Retrieve a deck by id' })
@Get() @ApiUnauthorizedResponse({ description: 'Unauthorized' })
findAll(@Query() query: GetAllDecksDto, @Req() req) {
const finalQuery = Pagination.getPaginationData(query)
return this.commandBus.execute(new GetAllDecksCommand({ ...finalQuery, userId: req.user.id }))
}
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get(':id') @Get(':id')
findOne(@Param('id') id: string) { findOne(@Param('id') id: string): Promise<Deck> {
return this.commandBus.execute(new GetDeckByIdCommand(id)) return this.commandBus.execute(new GetDeckByIdCommand(id))
} }
@ApiConsumes('multipart/form-data')
@ApiOperation({ description: 'Update a deck', summary: 'Update a deck' })
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
@ApiNotFoundResponse({ description: 'Deck not found' })
@UseGuards(JwtAuthGuard)
@UseInterceptors(FileFieldsInterceptor([{ name: 'cover', maxCount: 1 }]))
@Patch(':id')
update(
@Param('id') id: string,
@UploadedFiles()
files: {
cover: Express.Multer.File[]
},
@Body() updateDeckDto: UpdateDeckDto,
@Req() req
): Promise<Deck> {
return this.commandBus.execute(
new UpdateDeckCommand(id, updateDeckDto, req.user.id, files?.cover?.[0])
)
}
@UseGuards(JwtAuthGuard)
@ApiOperation({ description: 'Delete a deck', summary: 'Delete a deck' })
@ApiOkResponse({ description: 'Deck deleted', type: Deck })
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
@ApiNotFoundResponse({ description: 'Deck not found' })
@Delete(':id')
remove(@Param('id') id: string, @Req() req): Promise<Deck> {
return this.commandBus.execute(new DeleteDeckByIdCommand(id, req.user.id))
}
@ApiOperation({
description: 'Retrieve paginated cards in a deck',
summary: 'Retrieve cards in a deck',
})
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get(':id/cards') @Get(':id/cards')
findCardsInDeck(@Param('id') id: string, @Req() req, @Query() query: GetAllCardsInDeckDto) { findCardsInDeck(
@Param('id') id: string,
@Req() req,
@Query() query: GetAllCardsInDeckDto
): Promise<PaginatedCards> {
const finalQuery = Pagination.getPaginationData(query) const finalQuery = Pagination.getPaginationData(query)
return this.commandBus.execute(new GetAllCardsInDeckCommand(req.user.id, id, finalQuery)) return this.commandBus.execute(new GetAllCardsInDeckCommand(req.user.id, id, finalQuery))
} }
@UseGuards(JwtAuthGuard) @ApiConsumes('multipart/form-data')
@Get(':id/learn') @ApiOperation({ description: 'Create card in a deck', summary: 'Create a card' })
findRandomCardInDeck(@Param('id') id: string, @Req() req) { @ApiUnauthorizedResponse({ description: 'Unauthorized' })
return this.commandBus.execute(new GetRandomCardInDeckCommand(req.user.id, id)) @ApiNotFoundResponse({ description: 'Deck not found' })
}
@UseGuards(JwtAuthGuard)
@Post(':id/learn')
saveGrade(@Param('id') id: string, @Req() req, @Body() body: any) {
return this.commandBus.execute(
new SaveGradeCommand(req.user.id, { cardId: body.cardId, grade: body.grade })
)
}
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@UseInterceptors( @UseInterceptors(
FileFieldsInterceptor([ FileFieldsInterceptor([
@@ -108,32 +157,36 @@ export class DecksController {
@UploadedFiles() @UploadedFiles()
files: { questionImg: Express.Multer.File[]; answerImg: Express.Multer.File[] }, files: { questionImg: Express.Multer.File[]; answerImg: Express.Multer.File[] },
@Body() card: CreateCardDto @Body() card: CreateCardDto
) { ): Promise<Card> {
return this.commandBus.execute( return this.commandBus.execute(
new CreateCardCommand(req.user.id, id, card, files.answerImg?.[0], files.questionImg?.[0]) new CreateCardCommand(req.user.id, id, card, files.answerImg?.[0], files.questionImg?.[0])
) )
} }
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@UseInterceptors(FileFieldsInterceptor([{ name: 'cover', maxCount: 1 }])) @ApiOperation({
@Patch(':id') description: 'Retrieve a random card in a deck. The cards priority is based on the grade',
update( summary: 'Retrieve a random card',
@Param('id') id: string, })
@UploadedFiles() @Get(':id/learn')
files: { findRandomCardInDeck(@Param('id') id: string, @Req() req): Promise<Card> {
cover: Express.Multer.File[] return this.commandBus.execute(new GetRandomCardInDeckCommand(req.user.id, id))
},
@Body() updateDeckDto: UpdateDeckDto,
@Req() req
) {
return this.commandBus.execute(
new UpdateDeckCommand(id, updateDeckDto, req.user.id, files?.cover?.[0])
)
} }
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Delete(':id') @ApiUnauthorizedResponse({ description: 'Unauthorized' })
remove(@Param('id') id: string, @Req() req) { @ApiNotFoundResponse({ description: 'Card not found' })
return this.commandBus.execute(new DeleteDeckByIdCommand(id, req.user.id)) @HttpCode(HttpStatus.NO_CONTENT)
@ApiNoContentResponse({ description: 'Grade saved' })
@Post(':id/learn')
@ApiOperation({
description: '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> {
return this.commandBus.execute(
new SaveGradeCommand(req.user.id, { cardId: body.cardId, grade: body.grade })
)
} }
} }

View File

@@ -1,18 +1,25 @@
import { ApiHideProperty, ApiProperty } from '@nestjs/swagger'
import { Transform } from 'class-transformer' import { Transform } from 'class-transformer'
import { IsBoolean, IsOptional, Length } from 'class-validator' import { IsBoolean, IsOptional, Length } from 'class-validator'
export class CreateDeckDto { export class CreateDeckDto {
@Length(3, 30) @Length(3, 30)
name: string name: string
/**
* Cover image (binary)
*/
@IsOptional() @IsOptional()
@Length(0, 0) @ApiProperty({ type: 'string', format: 'binary' })
cover?: string cover?: string
/**
* Private decks are not visible to other users
*/
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
@Transform((val: string) => [true, 'true', 1, '1'].indexOf(val) > -1) @Transform((val: string) => [true, 'true', 1, '1'].indexOf(val) > -1)
isPrivate?: boolean isPrivate?: boolean
@ApiHideProperty()
userId: string userId: string
} }

View File

@@ -1,3 +1,4 @@
import { ApiHideProperty } from '@nestjs/swagger'
import { IsUUID } from 'class-validator' import { IsUUID } from 'class-validator'
import { PaginationDto } from '../../../infrastructure/common/pagination/pagination.dto' import { PaginationDto } from '../../../infrastructure/common/pagination/pagination.dto'
@@ -10,15 +11,23 @@ export class GetAllDecksDto extends PaginationDto {
@IsOptionalOrEmptyString() @IsOptionalOrEmptyString()
maxCardsCount?: string maxCardsCount?: string
/** Search by deck name */
@IsOptionalOrEmptyString() @IsOptionalOrEmptyString()
name?: string name?: string
/** Filter by deck authorId */
@IsOptionalOrEmptyString() @IsOptionalOrEmptyString()
@IsUUID(4) @IsUUID(4)
authorId?: string authorId?: string
userId: string @ApiHideProperty()
userId?: string
/** A string that represents the name of the field to order by and the order direction.
* The format is: "field_name-order_direction".
* Available directions: "asc" and "desc".
* @example "name-desc"
* */
@IsOrderBy() @IsOrderBy()
orderBy?: string | null orderBy?: string | null
} }

View File

@@ -1,4 +1,5 @@
import { PartialType } from '@nestjs/mapped-types' import { PartialType } from '@nestjs/mapped-types'
import { ApiProperty } from '@nestjs/swagger'
import { IsBoolean } from 'class-validator' import { IsBoolean } from 'class-validator'
import { IsOptionalOrEmptyString } from '../../../infrastructure/decorators' import { IsOptionalOrEmptyString } from '../../../infrastructure/decorators'
@@ -7,12 +8,13 @@ import { CreateDeckDto } from './create-deck.dto'
export class UpdateDeckDto extends PartialType(CreateDeckDto) { export class UpdateDeckDto extends PartialType(CreateDeckDto) {
@IsOptionalOrEmptyString() @IsOptionalOrEmptyString()
name: string name?: string
@IsOptionalOrEmptyString() @IsOptionalOrEmptyString()
@IsBoolean() @IsBoolean()
isPrivate: boolean isPrivate?: boolean
@IsOptionalOrEmptyString() @IsOptionalOrEmptyString()
cover: string @ApiProperty({ type: 'string', format: 'binary' })
cover?: string
} }

View File

@@ -1 +1,26 @@
export class Deck {} import { Pagination } from '../../../infrastructure/common/pagination/pagination.dto'
export class Deck {
id: string
userId: string
name: string
isPrivate: boolean
shots: number
cover: string | null
rating: number
created: Date
updated: Date
cardsCount: number
author: DeckAuthor
}
export class DeckAuthor {
id: string
name: string
}
export class PaginatedDecks {
items: Deck[]
pagination: Pagination
maxCardsCount: number
}

View File

@@ -4,6 +4,7 @@ import { createPrismaOrderBy } from '../../../infrastructure/common/helpers/get-
import { Pagination } from '../../../infrastructure/common/pagination/pagination.service' import { Pagination } from '../../../infrastructure/common/pagination/pagination.service'
import { PrismaService } from '../../../prisma.service' import { PrismaService } from '../../../prisma.service'
import { GetAllDecksDto } from '../dto' import { GetAllDecksDto } from '../dto'
import { Deck, PaginatedDecks } from '../entities/deck.entity'
@Injectable() @Injectable()
export class DecksRepository { export class DecksRepository {
@@ -21,7 +22,7 @@ export class DecksRepository {
userId: string userId: string
cover?: string cover?: string
isPrivate?: boolean isPrivate?: boolean
}) { }): Promise<Deck> {
try { try {
return await this.prisma.deck.create({ return await this.prisma.deck.create({
data: { data: {
@@ -35,6 +36,14 @@ export class DecksRepository {
cover, cover,
isPrivate, isPrivate,
}, },
include: {
author: {
select: {
id: true,
name: true,
},
},
},
}) })
} catch (e) { } catch (e) {
this.logger.error(e?.message) this.logger.error(e?.message)
@@ -51,9 +60,7 @@ export class DecksRepository {
minCardsCount, minCardsCount,
maxCardsCount, maxCardsCount,
orderBy, orderBy,
}: GetAllDecksDto) { }: GetAllDecksDto): Promise<PaginatedDecks> {
console.log(minCardsCount)
console.log(Number(minCardsCount))
try { try {
const where = { const where = {
cardsCount: { cardsCount: {
@@ -163,13 +170,21 @@ export class DecksRepository {
public async updateDeckById( public async updateDeckById(
id: string, id: string,
data: { name?: string; cover?: string; isPrivate?: boolean } data: { name?: string; cover?: string; isPrivate?: boolean }
) { ): Promise<Deck> {
try { try {
return await this.prisma.deck.update({ return await this.prisma.deck.update({
where: { where: {
id, id,
}, },
data, data,
include: {
author: {
select: {
id: true,
name: true,
},
},
},
}) })
} catch (e) { } catch (e) {
this.logger.error(e?.message) this.logger.error(e?.message)

View File

@@ -2,6 +2,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
import { FileUploadService } from '../../../infrastructure/file-upload-service/file-upload.service' import { FileUploadService } from '../../../infrastructure/file-upload-service/file-upload.service'
import { CreateCardDto } from '../../cards/dto' import { CreateCardDto } from '../../cards/dto'
import { Card } from '../../cards/entities/cards.entity'
import { CardsRepository } from '../../cards/infrastructure/cards.repository' import { CardsRepository } from '../../cards/infrastructure/cards.repository'
export class CreateCardCommand { export class CreateCardCommand {
@@ -21,7 +22,7 @@ export class CreateCardHandler implements ICommandHandler<CreateCardCommand> {
private readonly fileUploadService: FileUploadService private readonly fileUploadService: FileUploadService
) {} ) {}
async execute(command: CreateCardCommand) { async execute(command: CreateCardCommand): Promise<Card> {
let questionImg, answerImg let questionImg, answerImg
if (command.questionImg && command.answerImg) { if (command.questionImg && command.answerImg) {

View File

@@ -2,6 +2,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
import { FileUploadService } from '../../../infrastructure/file-upload-service/file-upload.service' import { FileUploadService } from '../../../infrastructure/file-upload-service/file-upload.service'
import { CreateDeckDto } from '../dto' import { CreateDeckDto } from '../dto'
import { Deck } from '../entities/deck.entity'
import { DecksRepository } from '../infrastructure/decks.repository' import { DecksRepository } from '../infrastructure/decks.repository'
export class CreateDeckCommand { export class CreateDeckCommand {
@@ -15,7 +16,7 @@ export class CreateDeckHandler implements ICommandHandler<CreateDeckCommand> {
private readonly fileUploadService: FileUploadService private readonly fileUploadService: FileUploadService
) {} ) {}
async execute(command: CreateDeckCommand) { async execute(command: CreateDeckCommand): Promise<Deck> {
let cover let cover
if (command.cover) { if (command.cover) {

View File

@@ -1,4 +1,4 @@
import { BadRequestException, NotFoundException } from '@nestjs/common' import { ForbiddenException, NotFoundException } from '@nestjs/common'
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs' import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
import { DecksRepository } from '../infrastructure/decks.repository' import { DecksRepository } from '../infrastructure/decks.repository'
@@ -16,7 +16,7 @@ export class DeleteDeckByIdHandler implements ICommandHandler<DeleteDeckByIdComm
if (!deck) throw new NotFoundException(`Deck with id ${command.id} not found`) if (!deck) throw new NotFoundException(`Deck with id ${command.id} not found`)
if (deck.userId !== command.userId) { if (deck.userId !== command.userId) {
throw new BadRequestException(`You can't delete a deck that you don't own`) throw new ForbiddenException(`You can't delete a deck that you don't own`)
} }
return await this.deckRepository.deleteDeckById(command.id) return await this.deckRepository.deleteDeckById(command.id)

View File

@@ -2,6 +2,7 @@ import { ForbiddenException, NotFoundException } from '@nestjs/common'
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs' import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
import { GetAllCardsInDeckDto } from '../../cards/dto' import { GetAllCardsInDeckDto } from '../../cards/dto'
import { PaginatedCards } from '../../cards/entities/cards.entity'
import { CardsRepository } from '../../cards/infrastructure/cards.repository' import { CardsRepository } from '../../cards/infrastructure/cards.repository'
import { DecksRepository } from '../infrastructure/decks.repository' import { DecksRepository } from '../infrastructure/decks.repository'
@@ -20,7 +21,7 @@ export class GetAllCardsInDeckHandler implements ICommandHandler<GetAllCardsInDe
private readonly decksRepository: DecksRepository private readonly decksRepository: DecksRepository
) {} ) {}
async execute(command: GetAllCardsInDeckCommand) { async execute(command: GetAllCardsInDeckCommand): Promise<PaginatedCards> {
const deck = await this.decksRepository.findDeckById(command.deckId) const deck = await this.decksRepository.findDeckById(command.deckId)
if (!deck) throw new NotFoundException(`Deck with id ${command.deckId} not found`) if (!deck) throw new NotFoundException(`Deck with id ${command.deckId} not found`)

View File

@@ -1,6 +1,7 @@
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs' import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
import { GetAllDecksDto } from '../dto' import { GetAllDecksDto } from '../dto'
import { PaginatedDecks } from '../entities/deck.entity'
import { DecksRepository } from '../infrastructure/decks.repository' import { DecksRepository } from '../infrastructure/decks.repository'
export class GetAllDecksCommand { export class GetAllDecksCommand {
@@ -11,7 +12,7 @@ export class GetAllDecksCommand {
export class GetAllDecksHandler implements ICommandHandler<GetAllDecksCommand> { export class GetAllDecksHandler implements ICommandHandler<GetAllDecksCommand> {
constructor(private readonly deckRepository: DecksRepository) {} constructor(private readonly deckRepository: DecksRepository) {}
async execute(command: GetAllDecksCommand) { async execute(command: GetAllDecksCommand): Promise<PaginatedDecks> {
return await this.deckRepository.findAllDecks(command.params) return await this.deckRepository.findAllDecks(command.params)
} }
} }

View File

@@ -3,6 +3,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
import { Prisma } from '@prisma/client' import { Prisma } from '@prisma/client'
import { pick } from 'remeda' import { pick } from 'remeda'
import { Card } from '../../cards/entities/cards.entity'
import { CardsRepository } from '../../cards/infrastructure/cards.repository' import { CardsRepository } from '../../cards/infrastructure/cards.repository'
import { DecksRepository } from '../infrastructure/decks.repository' import { DecksRepository } from '../infrastructure/decks.repository'
@@ -19,7 +20,7 @@ export class GetRandomCardInDeckHandler implements ICommandHandler<GetRandomCard
private readonly decksRepository: DecksRepository private readonly decksRepository: DecksRepository
) {} ) {}
private async getSmartRandomCard(cards: Array<CardWithGrade>) { private async getSmartRandomCard(cards: Array<CardWithGrade>): Promise<Card> {
const selectionPool: Array<CardWithGrade> = [] const selectionPool: Array<CardWithGrade> = []
cards.forEach(card => { cards.forEach(card => {

View File

@@ -23,7 +23,7 @@ export class SaveGradeHandler implements ICommandHandler<SaveGradeCommand> {
private readonly gradesRepository: GradesRepository private readonly gradesRepository: GradesRepository
) {} ) {}
async execute(command: SaveGradeCommand) { async execute(command: SaveGradeCommand): Promise<void> {
const deck = await this.decksRepository.findDeckByCardId(command.args.cardId) const deck = await this.decksRepository.findDeckByCardId(command.args.cardId)
if (!deck) if (!deck)
@@ -33,7 +33,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`)
} }
return await this.gradesRepository.createGrade({ 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,

View File

@@ -3,6 +3,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
import { FileUploadService } from '../../../infrastructure/file-upload-service/file-upload.service' import { FileUploadService } from '../../../infrastructure/file-upload-service/file-upload.service'
import { UpdateDeckDto } from '../dto' import { UpdateDeckDto } from '../dto'
import { Deck } from '../entities/deck.entity'
import { DecksRepository } from '../infrastructure/decks.repository' import { DecksRepository } from '../infrastructure/decks.repository'
export class UpdateDeckCommand { export class UpdateDeckCommand {
@@ -21,7 +22,7 @@ export class UpdateDeckHandler implements ICommandHandler<UpdateDeckCommand> {
private readonly fileUploadService: FileUploadService private readonly fileUploadService: FileUploadService
) {} ) {}
async execute(command: UpdateDeckCommand) { async execute(command: UpdateDeckCommand): Promise<Deck> {
const deck = await this.deckRepository.findDeckById(command.deckId) const deck = await this.deckRepository.findDeckById(command.deckId)
if (!deck) { if (!deck) {