From 7dd70008859f0547b7263a1c0df2e5975bb590e5 Mon Sep 17 00:00:00 2001 From: GRZ4NA Date: Sat, 27 May 2023 02:29:40 +0200 Subject: [PATCH] solutions system --- apps/backend/src/app.controller.ts | 39 +++++++++++++- apps/backend/src/app.module.ts | 2 + .../src/solutions/dto/create-solution.dto.ts | 13 +++++ .../src/solutions/entities/solution.entity.ts | 53 +++++++++++++++++++ .../backend/src/solutions/solutions.module.ts | 12 +++++ .../src/solutions/solutions.service.ts | 40 ++++++++++++++ 6 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 apps/backend/src/solutions/dto/create-solution.dto.ts create mode 100644 apps/backend/src/solutions/entities/solution.entity.ts create mode 100644 apps/backend/src/solutions/solutions.module.ts create mode 100644 apps/backend/src/solutions/solutions.service.ts diff --git a/apps/backend/src/app.controller.ts b/apps/backend/src/app.controller.ts index cd9ac38..b0b7c15 100644 --- a/apps/backend/src/app.controller.ts +++ b/apps/backend/src/app.controller.ts @@ -13,10 +13,14 @@ import { IssuesService } from './issues/issues.service'; import { IssueType } from './issues/entities/issue-type.enum'; import { CreateIssueDto } from './issues/dto/create-issue.dto'; import { CreateIssueCommentDto } from './issues/dto/create-issue-comment.dto'; +import { SolutionsService } from './solutions/solutions.service'; @Controller() export class AppController { - constructor(private readonly issues: IssuesService) {} + constructor( + private readonly issues: IssuesService, + private readonly solutions: SolutionsService, + ) {} @Get('/issues/problems') listProblems() { @@ -36,7 +40,14 @@ export class AppController { async getIssue(@Param('id', ParseUUIDPipe) id: string) { const data = await this.issues.findOne(id).then((i) => i.toDto()); const comments = await this.issues.getCommentsForIssue(id); - return { data, comments }; + const solution = + (await this.solutions.getForIssue(id))?.toList() ?? undefined; + return { data, comments, solution }; + } + + @Get('/issues/:id/solution') + async getSolution(@Param('id', ParseUUIDPipe) id: string) { + return this.solutions.getForIssue(id).then((i) => i.toDto()); } @Post('/issues') @@ -64,6 +75,18 @@ export class AppController { await this.issues.dislike(id); } + @Post('/issues/:id/solution/like') + @HttpCode(HttpStatus.ACCEPTED) + async addLikeToSolution(@Param('id', ParseUUIDPipe) id: string) { + await this.solutions.like(id); + } + + @Post('/issues/:id/solution/dislike') + @HttpCode(HttpStatus.ACCEPTED) + async addDislikeToSolution(@Param('id', ParseUUIDPipe) id: string) { + await this.solutions.dislike(id); + } + @Delete('/issues/:id/like') @HttpCode(HttpStatus.ACCEPTED) async removeLike(@Param('id', ParseUUIDPipe) id: string) { @@ -75,4 +98,16 @@ export class AppController { async removeDislike(@Param('id', ParseUUIDPipe) id: string) { await this.issues.dislike(id, true); } + + @Delete('/issues/:id/solution/like') + @HttpCode(HttpStatus.ACCEPTED) + async removeLikeToSolution(@Param('id', ParseUUIDPipe) id: string) { + await this.solutions.like(id, true); + } + + @Delete('/issues/:id/solution/dislike') + @HttpCode(HttpStatus.ACCEPTED) + async removeDislikeToSolution(@Param('id', ParseUUIDPipe) id: string) { + await this.solutions.dislike(id, true); + } } diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index fbe3c89..da2bb68 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { IssuesModule } from './issues/issues.module'; import { AppController } from './app.controller'; +import { SolutionsModule } from './solutions/solutions.module'; @Module({ controllers: [AppController], @@ -17,6 +18,7 @@ import { AppController } from './app.controller'; synchronize: process.env.NODE_ENV !== 'production', }), IssuesModule, + SolutionsModule, ], }) export class AppModule {} diff --git a/apps/backend/src/solutions/dto/create-solution.dto.ts b/apps/backend/src/solutions/dto/create-solution.dto.ts new file mode 100644 index 0000000..aef3ca7 --- /dev/null +++ b/apps/backend/src/solutions/dto/create-solution.dto.ts @@ -0,0 +1,13 @@ +import { IsString, MaxLength, MinLength } from 'class-validator'; + +export class CreateSolutionDto { + @IsString() + @MinLength(1) + @MaxLength(32) + title: string; + + @IsString() + @MinLength(1) + @MaxLength(255) + description: string; +} diff --git a/apps/backend/src/solutions/entities/solution.entity.ts b/apps/backend/src/solutions/entities/solution.entity.ts new file mode 100644 index 0000000..9f4eca8 --- /dev/null +++ b/apps/backend/src/solutions/entities/solution.entity.ts @@ -0,0 +1,53 @@ +import { Issue } from 'src/issues/entities/issue.entity'; +import { + Column, + CreateDateColumn, + Entity, + ManyToOne, + PrimaryGeneratedColumn, +} from 'typeorm'; + +@Entity('solutions') +export class Solution { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column() + title: string; + + @Column() + username: string; + + @Column() + description: string; + + @CreateDateColumn() + createdAt: Date; + + @ManyToOne(() => Issue, { lazy: true, onDelete: 'CASCADE' }) + issue: Promise; + + @Column({ default: 0 }) + likes: number; + + @Column({ default: 0 }) + dislikes: number; + + async toDto() { + return { + ...this.toList(), + description: this.description, + issue: (await this.issue).toList(), + }; + } + + toList() { + return { + title: this.title, + username: this.username, + likes: this.likes, + dislikes: this.dislikes, + createdAt: this.createdAt.toISOString(), + }; + } +} diff --git a/apps/backend/src/solutions/solutions.module.ts b/apps/backend/src/solutions/solutions.module.ts new file mode 100644 index 0000000..b98060e --- /dev/null +++ b/apps/backend/src/solutions/solutions.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { SolutionsService } from './solutions.service'; +import { IssuesModule } from 'src/issues/issues.module'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Solution } from './entities/solution.entity'; + +@Module({ + imports: [IssuesModule, TypeOrmModule.forFeature([Solution])], + providers: [SolutionsService], + exports: [SolutionsService], +}) +export class SolutionsModule {} diff --git a/apps/backend/src/solutions/solutions.service.ts b/apps/backend/src/solutions/solutions.service.ts new file mode 100644 index 0000000..44b9288 --- /dev/null +++ b/apps/backend/src/solutions/solutions.service.ts @@ -0,0 +1,40 @@ +import { ConflictException, Injectable } from '@nestjs/common'; +import { CreateSolutionDto } from './dto/create-solution.dto'; +import { IssuesService } from 'src/issues/issues.service'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Solution } from './entities/solution.entity'; +import { Repository } from 'typeorm'; + +@Injectable() +export class SolutionsService { + constructor( + @InjectRepository(Solution) + private readonly repository: Repository, + private readonly issues: IssuesService, + ) {} + + async create(issueId: string, createSolutionDto: CreateSolutionDto) { + if ((await this.getForIssue(issueId)) !== null) + throw new ConflictException('This issue already has a solution'); + const solution = this.repository.create({ ...createSolutionDto }); + const issue = await this.issues.findOne(issueId); + solution.issue = Promise.resolve(issue); + return this.repository.save(solution); + } + + async getForIssue(issueId: string): Promise { + return this.repository.findOneBy({ issue: { id: issueId } }); + } + + async like(issueId: string, remove?: boolean) { + const ent = await this.getForIssue(issueId); + ent.likes = remove ? ent.likes - 1 : ent.likes + 1; + return this.repository.save(ent); + } + + async dislike(issueId: string, remove?: boolean) { + const ent = await this.getForIssue(issueId); + ent.dislikes = remove ? ent.dislikes - 1 : ent.dislikes + 1; + return this.repository.save(ent); + } +}