diff --git a/package.json b/package.json index adcb35a..360e6f8 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "@nestjs/testing": "^9.4.1", "@types/express": "^4.17.17", "@types/jest": "29.5.1", + "@types/multer": "^1.4.7", "@types/node": "18.16.12", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.6", diff --git a/src/modules/cards/cards.controller.ts b/src/modules/cards/cards.controller.ts index f3e7cce..9ae9ed5 100644 --- a/src/modules/cards/cards.controller.ts +++ b/src/modules/cards/cards.controller.ts @@ -1,9 +1,21 @@ -import { Body, Controller, Delete, Get, Param, Patch, Req, UseGuards } from '@nestjs/common' +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Req, + UploadedFiles, + UseGuards, + UseInterceptors, +} from '@nestjs/common' import { CardsService } from './cards.service' import { UpdateCardDto } from './dto/update-card.dto' import { CommandBus } from '@nestjs/cqrs' -import { DeleteCardByIdCommand, GetDeckByIdCommand, UpdateDeckCommand } from './use-cases' +import { DeleteCardByIdCommand, GetDeckByIdCommand } from './use-cases' import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard' +import { FileFieldsInterceptor } from '@nestjs/platform-express' @Controller('cards') export class CardsController { @@ -16,9 +28,23 @@ export class CardsController { } @UseGuards(JwtAuthGuard) + @UseInterceptors( + FileFieldsInterceptor([ + { name: 'questionImg', maxCount: 1 }, + { name: 'answerImg', maxCount: 1 }, + ]) + ) @Patch(':id') - update(@Param('id') id: string, @Body() updateDeckDto: UpdateCardDto, @Req() req) { - return this.commandBus.execute(new UpdateDeckCommand(id, updateDeckDto, req.user.id)) + update( + @Param('id') id: string, + @Req() req, + @UploadedFiles() + files: { questionImg: Express.Multer.File[]; answerImg: Express.Multer.File[] }, + @Body() body: UpdateCardDto + ) { + console.log({ body }) + console.log(files) + // return this.commandBus.execute(new UpdateCardCommand(id, body, req.user.id)) } @UseGuards(JwtAuthGuard) diff --git a/src/modules/cards/cards.module.ts b/src/modules/cards/cards.module.ts index 85039c3..7a6cebe 100644 --- a/src/modules/cards/cards.module.ts +++ b/src/modules/cards/cards.module.ts @@ -2,10 +2,10 @@ import { Module } from '@nestjs/common' import { CardsService } from './cards.service' import { CardsController } from './cards.controller' import { CqrsModule } from '@nestjs/cqrs' -import { DeleteCardByIdHandler, GetDeckByIdHandler, UpdateDeckHandler } from './use-cases' +import { DeleteCardByIdHandler, GetDeckByIdHandler, UpdateCardHandler } from './use-cases' import { CardsRepository } from './infrastructure/cards.repository' -const commandHandlers = [GetDeckByIdHandler, DeleteCardByIdHandler, UpdateDeckHandler] +const commandHandlers = [GetDeckByIdHandler, DeleteCardByIdHandler, UpdateCardHandler] @Module({ imports: [CqrsModule], diff --git a/src/modules/cards/dto/create-card.dto.ts b/src/modules/cards/dto/create-card.dto.ts index d0d31ad..7b24070 100644 --- a/src/modules/cards/dto/create-card.dto.ts +++ b/src/modules/cards/dto/create-card.dto.ts @@ -1,4 +1,4 @@ -import { Length } from 'class-validator' +import { IsOptional, Length } from 'class-validator' export class CreateCardDto { @Length(3, 500) @@ -6,4 +6,20 @@ export class CreateCardDto { @Length(3, 500) answer: string + + @IsOptional() + @Length(3, 500) + questionImg?: string + + @IsOptional() + @Length(3, 500) + answerImg?: string + + @IsOptional() + @Length(3, 500) + questionVideo?: string + + @IsOptional() + @Length(3, 500) + answerVideo?: string } diff --git a/src/modules/cards/dto/update-card.dto.ts b/src/modules/cards/dto/update-card.dto.ts index b9e5c31..33717aa 100644 --- a/src/modules/cards/dto/update-card.dto.ts +++ b/src/modules/cards/dto/update-card.dto.ts @@ -1,16 +1,4 @@ import { PartialType } from '@nestjs/mapped-types' import { CreateCardDto } from './create-card.dto' -import { IsOptionalOrEmptyString } from '../../../infrastructure/decorators/is-optional-or-empty-string' -import { IsBoolean } from 'class-validator' -export class UpdateCardDto extends PartialType(CreateCardDto) { - @IsOptionalOrEmptyString() - name: string - - @IsOptionalOrEmptyString() - @IsBoolean() - isPrivate: boolean - - @IsOptionalOrEmptyString() - cover: string -} +export class UpdateCardDto extends PartialType(CreateCardDto) {} diff --git a/src/modules/cards/infrastructure/cards.repository.ts b/src/modules/cards/infrastructure/cards.repository.ts index df42da3..f03443e 100644 --- a/src/modules/cards/infrastructure/cards.repository.ts +++ b/src/modules/cards/infrastructure/cards.repository.ts @@ -4,6 +4,7 @@ import { GetAllCardsInDeckDto } from '../dto/get-all-cards.dto' import { CreateCardDto } from '../dto/create-card.dto' import { Pagination } from '../../../infrastructure/common/pagination/pagination.service' import { createPrismaOrderBy } from '../../../infrastructure/common/helpers/get-order-by-object' +import { UpdateCardDto } from '../dto/update-card.dto' @Injectable() export class CardsRepository { @@ -146,12 +147,9 @@ export class CardsRepository { } } - public async updateDeckById( - id: string, - data: { name?: string; cover?: string; isPrivate?: boolean } - ) { + public async updateCardById(id: string, data: UpdateCardDto) { try { - return await this.prisma.deck.update({ + return await this.prisma.card.update({ where: { id, }, diff --git a/src/modules/cards/use-cases/index.ts b/src/modules/cards/use-cases/index.ts index 23872ca..8b4434c 100644 --- a/src/modules/cards/use-cases/index.ts +++ b/src/modules/cards/use-cases/index.ts @@ -1,3 +1,3 @@ export * from './get-deck-by-id-use-case' export * from './delete-card-by-id-use-case' -export * from './update-deck-use-case' +export * from './update-card-use-case' diff --git a/src/modules/cards/use-cases/update-card-use-case.ts b/src/modules/cards/use-cases/update-card-use-case.ts new file mode 100644 index 0000000..faca1bd --- /dev/null +++ b/src/modules/cards/use-cases/update-card-use-case.ts @@ -0,0 +1,29 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs' +import { CardsRepository } from '../infrastructure/cards.repository' +import { UpdateCardDto } from '../dto/update-card.dto' +import { BadRequestException, NotFoundException } from '@nestjs/common' + +export class UpdateCardCommand { + constructor( + public readonly cardId: string, + public readonly card: UpdateCardDto, + public readonly userId: string + ) {} +} + +@CommandHandler(UpdateCardCommand) +export class UpdateCardHandler implements ICommandHandler { + constructor(private readonly cardsRepository: CardsRepository) {} + + async execute(command: UpdateCardCommand) { + const card = await this.cardsRepository.findCardById(command.cardId) + + if (!card) throw new NotFoundException(`Card with id ${command.cardId} not found`) + + if (card.userId !== command.userId) { + throw new BadRequestException(`You can't change a card that you don't own`) + } + + return await this.cardsRepository.updateCardById(command.cardId, command.card) + } +} diff --git a/src/modules/cards/use-cases/update-deck-use-case.ts b/src/modules/cards/use-cases/update-deck-use-case.ts deleted file mode 100644 index d12ef4a..0000000 --- a/src/modules/cards/use-cases/update-deck-use-case.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { CommandHandler, ICommandHandler } from '@nestjs/cqrs' -import { CardsRepository } from '../infrastructure/cards.repository' -import { UpdateCardDto } from '../dto/update-card.dto' -import { BadRequestException, NotFoundException } from '@nestjs/common' - -export class UpdateDeckCommand { - constructor( - public readonly deckId: string, - public readonly deck: UpdateCardDto, - public readonly userId: string - ) {} -} - -@CommandHandler(UpdateDeckCommand) -export class UpdateDeckHandler implements ICommandHandler { - constructor(private readonly deckRepository: CardsRepository) {} - - async execute(command: UpdateDeckCommand) { - const deck = await this.deckRepository.findCardById(command.deckId) - - if (!deck) throw new NotFoundException(`Deck with id ${command.deckId} not found`) - - if (deck.userId !== command.userId) { - throw new BadRequestException(`You can't change a deck that you don't own`) - } - - return await this.deckRepository.updateDeckById(command.deckId, command.deck) - } -} diff --git a/src/modules/decks/use-cases/get-random-card-in-deck-use-case.ts b/src/modules/decks/use-cases/get-random-card-in-deck-use-case.ts index 14d7ce8..7421eea 100644 --- a/src/modules/decks/use-cases/get-random-card-in-deck-use-case.ts +++ b/src/modules/decks/use-cases/get-random-card-in-deck-use-case.ts @@ -8,13 +8,16 @@ import { pick } from 'remeda' export class GetRandomCardInDeckCommand { constructor(public readonly userId: string, public readonly deckId: string) {} } + type CardWithGrade = Prisma.cardGetPayload<{ include: { grades: true } }> + @CommandHandler(GetRandomCardInDeckCommand) export class GetRandomCardInDeckHandler implements ICommandHandler { constructor( private readonly cardsRepository: CardsRepository, private readonly decksRepository: DecksRepository ) {} + private async getSmartRandomCard(cards: Array) { const selectionPool: Array = [] cards.forEach(card => { diff --git a/yarn.lock b/yarn.lock index 60dc193..f13f7ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1935,6 +1935,13 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== +"@types/multer@^1.4.7": + version "1.4.7" + resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.7.tgz#89cf03547c28c7bbcc726f029e2a76a7232cc79e" + integrity sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA== + dependencies: + "@types/express" "*" + "@types/node@*": version "20.4.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9"