From 7383ce697773885033a62403ca6a6afca1767a87 Mon Sep 17 00:00:00 2001 From: legonzaur Date: Sat, 31 May 2025 20:21:57 +0200 Subject: [PATCH] first version --- .vscode/launch.json | 20 ++++ src/app.module.ts | 24 ++--- src/main.ts | 1 + src/polls/dto/answer.dto.ts | 15 --- src/polls/dto/choice.dto.ts | 12 --- src/polls/dto/poll.dto.ts | 36 ------- src/polls/dto/question.dto.ts | 19 ---- src/polls/dto/questionOptions.dto.ts | 0 src/polls/entities/answer.entity.ts | 36 ------- src/polls/entities/choice.entity.ts | 14 --- src/polls/entities/inviteCode.entity.ts | 29 ------ src/polls/entities/poll.entity.ts | 40 -------- src/polls/entities/question.entity.ts | 53 ---------- src/polls/entities/questionOptions.entity.ts | 16 ---- src/polls/entities/user.entity.ts | 10 -- src/polls/polls.controller.ts | 96 ------------------- src/polls/polls.module.ts | 13 --- src/polls/polls.service.ts | 50 ---------- src/polls/questions.controller.ts | 52 ---------- src/polls/questions.service.ts | 20 ---- src/vote/vote.controller.spec.ts | 18 ++++ src/vote/vote.controller.ts | 24 +++++ src/vote/vote.dto.ts | 9 ++ src/vote/vote.entity.ts | 37 +++++++ src/vote/vote.module.ts | 12 +++ .../vote.service.spec.ts} | 10 +- src/vote/vote.service.ts | 54 +++++++++++ 27 files changed, 186 insertions(+), 534 deletions(-) create mode 100644 .vscode/launch.json delete mode 100644 src/polls/dto/answer.dto.ts delete mode 100644 src/polls/dto/choice.dto.ts delete mode 100644 src/polls/dto/poll.dto.ts delete mode 100644 src/polls/dto/question.dto.ts delete mode 100644 src/polls/dto/questionOptions.dto.ts delete mode 100644 src/polls/entities/answer.entity.ts delete mode 100644 src/polls/entities/choice.entity.ts delete mode 100644 src/polls/entities/inviteCode.entity.ts delete mode 100644 src/polls/entities/poll.entity.ts delete mode 100644 src/polls/entities/question.entity.ts delete mode 100644 src/polls/entities/questionOptions.entity.ts delete mode 100644 src/polls/entities/user.entity.ts delete mode 100644 src/polls/polls.controller.ts delete mode 100644 src/polls/polls.module.ts delete mode 100644 src/polls/polls.service.ts delete mode 100644 src/polls/questions.controller.ts delete mode 100644 src/polls/questions.service.ts create mode 100644 src/vote/vote.controller.spec.ts create mode 100644 src/vote/vote.controller.ts create mode 100644 src/vote/vote.dto.ts create mode 100644 src/vote/vote.entity.ts create mode 100644 src/vote/vote.module.ts rename src/{polls/polls.service.spec.ts => vote/vote.service.spec.ts} (56%) create mode 100644 src/vote/vote.service.ts diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..04e2c71 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/src/main.ts", + "preLaunchTask": "npm: build", + "console": "integratedTerminal" + } + ] +} \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index ceeacdd..5ae1151 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,14 +3,10 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule, ConfigService } from '@nestjs/config'; -import { PollsModule } from './polls/polls.module'; -import { Answer } from './polls/entities/answer.entity'; -import { Choice } from './polls/entities/choice.entity'; -import { InviteCode } from './polls/entities/inviteCode.entity'; -import { Poll } from './polls/entities/poll.entity'; -import { Question } from './polls/entities/question.entity'; -import { QuestionOption } from './polls/entities/questionOptions.entity'; -import { User } from './polls/entities/user.entity'; +import { VoteController } from './vote/vote.controller'; +import { VoteService } from './vote/vote.service'; +import { Answer, Vote } from './vote/vote.entity'; +import { VoteModule } from './vote/vote.module'; void ConfigModule.forRoot({ envFilePath: '.env', @@ -28,17 +24,9 @@ const configService = new ConfigService(); username: configService.get('PG_USER'), database: configService.get('PG_DATABASE'), synchronize: configService.get('DEV_MODE') == 'true', - entities: [ - Answer, - Choice, - InviteCode, - Poll, - Question, - QuestionOption, - User, - ], + entities: [Vote, Answer], }), - PollsModule, + VoteModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/main.ts b/src/main.ts index 43de1c2..535001b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,6 +7,7 @@ async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); + app.enableCors({ origin: '*' }); const config = new DocumentBuilder() .setTitle('Vote au jugement majoritaire') diff --git a/src/polls/dto/answer.dto.ts b/src/polls/dto/answer.dto.ts deleted file mode 100644 index fb0d42d..0000000 --- a/src/polls/dto/answer.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Expose } from 'class-transformer'; -import { IsNumber, IsString } from 'class-validator'; - -export class AnswerCreateDto { - @Expose() - @IsNumber() - @ApiProperty() - questionId: number; - - @Expose() - @IsString() - @ApiProperty() - choice: string; -} diff --git a/src/polls/dto/choice.dto.ts b/src/polls/dto/choice.dto.ts deleted file mode 100644 index a6f791f..0000000 --- a/src/polls/dto/choice.dto.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Expose } from 'class-transformer'; - -export class ChoiceReadDto { - @Expose() - @ApiProperty() - value: string; - - @Expose() - @ApiProperty() - order: string; -} diff --git a/src/polls/dto/poll.dto.ts b/src/polls/dto/poll.dto.ts deleted file mode 100644 index 2252dd1..0000000 --- a/src/polls/dto/poll.dto.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Expose } from 'class-transformer'; -import { QuestionUpdateDto } from './question.dto'; -import { ApiProperty } from '@nestjs/swagger'; -import { IsUUID } from 'class-validator'; - -export class PollUpdateDTO { - @Expose() - @ApiProperty() - name: string; - - @Expose() - @ApiProperty() - description: string; - - @Expose() - @ApiProperty() - questions: QuestionUpdateDto[]; - - @Expose() - @IsUUID() - @ApiProperty() - publicCode: string; -} - -export class PollReadDTO extends PollUpdateDTO { - @Expose() - @ApiProperty() - published: boolean; -} - -export class PollReadAdminDTO extends PollReadDTO { - @Expose() - @IsUUID() - @ApiProperty() - editCode: string; -} diff --git a/src/polls/dto/question.dto.ts b/src/polls/dto/question.dto.ts deleted file mode 100644 index aa90217..0000000 --- a/src/polls/dto/question.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Expose } from 'class-transformer'; -import { ChoiceReadDto } from './choice.dto'; - -export class QuestionUpdateDto { - @Expose() - id: number; - - @Expose() - value: string; - - @Expose() - mandatory: boolean; - - @Expose() - order: number; - - @Expose() - choices: ChoiceReadDto[]; -} diff --git a/src/polls/dto/questionOptions.dto.ts b/src/polls/dto/questionOptions.dto.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/polls/entities/answer.entity.ts b/src/polls/entities/answer.entity.ts deleted file mode 100644 index ccabf41..0000000 --- a/src/polls/entities/answer.entity.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - Entity, - PrimaryGeneratedColumn, - ManyToOne, - Unique, - JoinColumn, -} from 'typeorm'; -import { Question } from './question.entity'; -import { User } from './user.entity'; -import { QuestionOption } from './questionOptions.entity'; -import { Choice } from './choice.entity'; - -@Entity() -@Unique(['questionOption', 'user']) -export class Answer { - @PrimaryGeneratedColumn({}) - id: number; - - @ManyToOne(() => Question, (question: Question) => question.answers) - @JoinColumn([{ name: 'questionId', referencedColumnName: 'id' }]) - question: Question; - - @ManyToOne(() => Choice) - @JoinColumn([{ name: 'choiceId', referencedColumnName: 'id' }]) - choice: Choice; - - @ManyToOne(() => QuestionOption) - @JoinColumn([ - { name: 'questionId', referencedColumnName: 'question' }, - { name: 'choiceId', referencedColumnName: 'choice' }, - ]) - questionOption: QuestionOption; - - @ManyToOne(() => User) - user: User; -} diff --git a/src/polls/entities/choice.entity.ts b/src/polls/entities/choice.entity.ts deleted file mode 100644 index 5f72222..0000000 --- a/src/polls/entities/choice.entity.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn, Unique } from 'typeorm'; - -@Entity() -@Unique(['value']) -export class Choice { - @PrimaryGeneratedColumn({}) - id: number; - - @Column() - value: string; - - @Column() - order: string; -} diff --git a/src/polls/entities/inviteCode.entity.ts b/src/polls/entities/inviteCode.entity.ts deleted file mode 100644 index 1f9d170..0000000 --- a/src/polls/entities/inviteCode.entity.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { - Entity, - Column, - PrimaryGeneratedColumn, - Unique, - Generated, - ManyToOne, -} from 'typeorm'; -import { Poll } from './poll.entity'; - -@Entity() -@Unique(['value']) -export class InviteCode { - @PrimaryGeneratedColumn({}) - id: number; - - @Column() - description: string; - - @Column({ nullable: true, default: null }) - maxUsages: number; - - @Column() - @Generated('uuid') - value: string; - - @ManyToOne(() => Poll, (poll) => poll.codes) - poll: Poll; -} diff --git a/src/polls/entities/poll.entity.ts b/src/polls/entities/poll.entity.ts deleted file mode 100644 index 3837ac6..0000000 --- a/src/polls/entities/poll.entity.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { - Entity, - Column, - PrimaryGeneratedColumn, - OneToMany, - Generated, - Unique, -} from 'typeorm'; -import { Question } from './question.entity'; -import { InviteCode } from './inviteCode.entity'; - -@Entity() -@Unique(['editCode']) -export class Poll { - @PrimaryGeneratedColumn({}) - id: number; - - @Column({ default: 'Sondage' }) - name: string; - - @Column({ nullable: true }) - description: string; - - @Column() - @Generated('uuid') - publicCode: string; - - @Column() - @Generated('uuid') - editCode: string; - - @Column({ default: false }) - published: boolean; - - @OneToMany(() => InviteCode, (code) => code.poll, { cascade: true }) - codes: InviteCode; - - @OneToMany(() => Question, (question) => question.poll, { cascade: true }) - questions: Question[]; -} diff --git a/src/polls/entities/question.entity.ts b/src/polls/entities/question.entity.ts deleted file mode 100644 index 0ee9c3e..0000000 --- a/src/polls/entities/question.entity.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - Entity, - Column, - PrimaryGeneratedColumn, - ManyToOne, - OneToMany, - Unique, - ManyToMany, - JoinTable, -} from 'typeorm'; -import { Poll } from './poll.entity'; -import { Answer } from './answer.entity'; -import { Choice } from './choice.entity'; - -@Entity() -@Unique(['poll', 'order']) -export class Question { - @PrimaryGeneratedColumn({}) - id: number; - - @Column() - value: string; - - @Column() - mandatory: boolean; - - @Column() - order: number; - - @ManyToOne(() => Poll, (poll: Poll) => poll.questions) - poll: Poll; - - // @OneToMany(() => QuestionOption, (questionOption) => questionOption.question) - // options: QuestionOption[]; - - @ManyToMany(() => Choice, { eager: true }) - @JoinTable({ - name: 'question_options', - joinColumn: { - name: 'question', - referencedColumnName: 'id', - }, - inverseJoinColumn: { - name: 'choice', - referencedColumnName: 'id', - }, - synchronize: false, - }) - choices: Choice[]; - - @OneToMany(() => Answer, (answer) => answer.question) - answers: Answer[]; -} diff --git a/src/polls/entities/questionOptions.entity.ts b/src/polls/entities/questionOptions.entity.ts deleted file mode 100644 index 16e2250..0000000 --- a/src/polls/entities/questionOptions.entity.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Entity, ManyToOne, PrimaryGeneratedColumn, Unique } from 'typeorm'; -import { Choice } from './choice.entity'; -import { Question } from './question.entity'; - -@Entity() -@Unique(['question', 'choice']) -export class QuestionOption { - @PrimaryGeneratedColumn() - id: number; - - @ManyToOne(() => Choice) - choice: Choice; - - @ManyToOne(() => Question) - question: Question; -} diff --git a/src/polls/entities/user.entity.ts b/src/polls/entities/user.entity.ts deleted file mode 100644 index 26d09f6..0000000 --- a/src/polls/entities/user.entity.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; - -@Entity() -export class User { - @PrimaryGeneratedColumn({}) - id: number; - - @Column() - username: string; -} diff --git a/src/polls/polls.controller.ts b/src/polls/polls.controller.ts deleted file mode 100644 index 8ba3ac2..0000000 --- a/src/polls/polls.controller.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { - Body, - Controller, - Get, - HttpException, - HttpStatus, - Param, - ParseUUIDPipe, - Post, -} from '@nestjs/common'; -import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { PollsService } from './polls.service'; -import { PollReadAdminDTO, PollReadDTO, PollUpdateDTO } from './dto/poll.dto'; -import { plainToInstance } from 'class-transformer'; - -@ApiTags('polls') -@Controller('polls') -export class PollController { - constructor(private readonly pollService: PollsService) {} - - @Post() - @ApiOperation({ summary: 'Create poll' }) - @ApiResponse({ - status: 201, - description: 'Poll successfully created.', - type: PollReadAdminDTO, - }) - async create(): Promise { - const poll = await this.pollService.create(); - return plainToInstance(PollReadAdminDTO, poll); - } - - @Post(':publicCode/:editCode') - @ApiOperation({ summary: 'Update a poll' }) - @ApiResponse({ - status: 201, - description: 'Poll successfully created.', - type: PollReadAdminDTO, - }) - async update( - @Param('publicCode', new ParseUUIDPipe()) publicCode: string, - @Param('editCode', new ParseUUIDPipe()) editCode: string, - @Body() pollUpdate: PollUpdateDTO, - ): Promise { - const poll = await this.pollService.readAdmin(publicCode, editCode); - if (!poll) { - throw new HttpException( - 'Poll not found or invalid editCode', - HttpStatus.NOT_FOUND, - ); - } - await this.pollService.update(poll, pollUpdate); - return plainToInstance(PollReadAdminDTO, poll); - } - - @Get(':publicCode') - @ApiOperation({ summary: 'Get public information about a poll' }) - @ApiResponse({ - status: 200, - description: 'Found', - type: PollReadDTO, - }) - async get( - @Param('publicCode', new ParseUUIDPipe()) publicCode: string, - ): Promise { - const poll = await this.pollService.read(publicCode); - if (!poll) { - throw new HttpException('Poll not found', HttpStatus.NOT_FOUND); - } - if (!poll?.published) { - throw new HttpException('Poll not yet published', HttpStatus.FORBIDDEN); - } - return plainToInstance(PollReadDTO, poll); - } - - @Get(':publicCode/:editCode') - @ApiOperation({ summary: 'Get admin information about a poll' }) - @ApiResponse({ - status: 200, - description: 'Found', - type: PollReadAdminDTO, - }) - async getAdmin( - @Param('publicCode', new ParseUUIDPipe()) publicCode: string, - @Param('editCode', new ParseUUIDPipe()) editCode: string, - ): Promise { - const poll = await this.pollService.readAdmin(publicCode, editCode); - if (!poll) { - throw new HttpException( - 'Poll not found or invalid editCode', - HttpStatus.NOT_FOUND, - ); - } - return plainToInstance(PollReadAdminDTO, poll); - } -} diff --git a/src/polls/polls.module.ts b/src/polls/polls.module.ts deleted file mode 100644 index c2317f0..0000000 --- a/src/polls/polls.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Poll } from './entities/poll.entity'; -import { PollsService } from './polls.service'; -import { PollController } from './polls.controller'; -import { InviteCode } from './entities/inviteCode.entity'; - -@Module({ - imports: [TypeOrmModule.forFeature([Poll, InviteCode])], - controllers: [PollController], - providers: [PollsService], -}) -export class PollsModule {} diff --git a/src/polls/polls.service.ts b/src/polls/polls.service.ts deleted file mode 100644 index 393944b..0000000 --- a/src/polls/polls.service.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Poll } from './entities/poll.entity'; -import { Repository } from 'typeorm'; -import { PollUpdateDTO } from './dto/poll.dto'; -import { InviteCode } from './entities/inviteCode.entity'; - -@Injectable() -export class PollsService { - constructor( - @InjectRepository(Poll) private pollRepository: Repository, - @InjectRepository(InviteCode) - private inviteRepository: Repository, - ) {} - - async create() { - const poll = this.pollRepository.create(); - await this.pollRepository.save(poll); - return poll; - } - - async update(poll: Poll, pollUpdate: PollUpdateDTO) { - if (poll.published) { - throw Error('This poll is closed !'); - } - await this.pollRepository.update(poll, pollUpdate); - } - - async read(pollId: string) { - const poll = await this.pollRepository.findOneBy({ publicCode: pollId }); - return poll; - } - - async readAdmin(pollId: string, editCode: string) { - const poll = await this.pollRepository.findOneBy({ - publicCode: pollId, - editCode: editCode, - }); - return poll; - } - - async hasAccess(poll: Poll, accessCode: string) { - return await this.inviteRepository.exists({ - where: { - poll: { id: poll.id }, - value: accessCode, - }, - }); - } -} diff --git a/src/polls/questions.controller.ts b/src/polls/questions.controller.ts deleted file mode 100644 index e5c1006..0000000 --- a/src/polls/questions.controller.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { - Body, - Controller, - HttpException, - HttpStatus, - Param, - ParseUUIDPipe, - Post, -} from '@nestjs/common'; -import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { PollsService } from './polls.service'; -import { PollReadAdminDTO } from './dto/poll.dto'; -import { QuestionsService } from './questions.service'; -import { AnswerCreateDto } from './dto/answer.dto'; - -@ApiTags('questions') -@Controller('questions') -export class QuestionsController { - constructor( - private readonly questionsService: QuestionsService, - private readonly pollService: PollsService, - ) {} - - @Post(':publicCode/:accessCode') - @ApiOperation({ summary: 'Vote for a question' }) - @ApiResponse({ - status: 200, - description: 'Vote successfully saved.', - type: PollReadAdminDTO, - }) - async vote( - @Param('publicCode', new ParseUUIDPipe()) publicCode: string, - @Param('accessCode', new ParseUUIDPipe()) accessCode: string, - @Body() answer: AnswerCreateDto, - ) { - const poll = await this.pollService.read(publicCode); - if (!poll) { - throw new HttpException('Poll not found', HttpStatus.NOT_FOUND); - } - if (!poll?.published) { - throw new HttpException('Poll not yet published', HttpStatus.FORBIDDEN); - } - if (!(await this.pollService.hasAccess(poll, accessCode))) { - throw new HttpException('Invalid access code', HttpStatus.UNAUTHORIZED); - } - const question = this.questionsService.get(poll, answer.questionId); - if (!question) { - throw new HttpException('Question not found', HttpStatus.BAD_REQUEST); - } - await this.questionsService.vote(question, null, answer.choice); - } -} diff --git a/src/polls/questions.service.ts b/src/polls/questions.service.ts deleted file mode 100644 index ca0aee3..0000000 --- a/src/polls/questions.service.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; - -import { Repository } from 'typeorm'; - -import { Question } from './entities/question.entity'; -import { Poll } from './entities/poll.entity'; - -@Injectable() -export class QuestionsService { - constructor( - @InjectRepository(Question) - private questionRepository: Repository, - ) {} - - get(poll: Poll, questionId: number) { - const question = poll.questions.find((q) => q.id == questionId); - return question; - } -} diff --git a/src/vote/vote.controller.spec.ts b/src/vote/vote.controller.spec.ts new file mode 100644 index 0000000..bfb6115 --- /dev/null +++ b/src/vote/vote.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { VoteController } from './vote.controller'; + +describe('VoteController', () => { + let controller: VoteController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [VoteController], + }).compile(); + + controller = module.get(VoteController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/vote/vote.controller.ts b/src/vote/vote.controller.ts new file mode 100644 index 0000000..0a293d0 --- /dev/null +++ b/src/vote/vote.controller.ts @@ -0,0 +1,24 @@ +import { Body, Controller, Get, Param, Post } from '@nestjs/common'; +import { VoteCreateDTO } from './vote.dto'; +import { VoteService } from './vote.service'; +import { ApiOperation } from '@nestjs/swagger'; + +@Controller('') +export class VoteController { + constructor(private readonly voteService: VoteService) {} + + @Post('vote/:accessCode') + @ApiOperation({ summary: 'Vote for a poll' }) + async vote( + @Param('accessCode') accessCode: string, + @Body() answer: VoteCreateDTO, + ) { + await this.voteService.create(answer); + } + + @Get('results/:accessCode') + @ApiOperation({ summary: 'Get results' }) + async results(@Param('accessCode') accessCode: string) { + return await this.voteService.getResults(); + } +} diff --git a/src/vote/vote.dto.ts b/src/vote/vote.dto.ts new file mode 100644 index 0000000..eaa782f --- /dev/null +++ b/src/vote/vote.dto.ts @@ -0,0 +1,9 @@ +export class VoteCreateDTO { + '1': string; + '2.1': string; +} + +export class VoteResultsDTO { + '1': number[]; + '2': number[]; +} diff --git a/src/vote/vote.entity.ts b/src/vote/vote.entity.ts new file mode 100644 index 0000000..db429b4 --- /dev/null +++ b/src/vote/vote.entity.ts @@ -0,0 +1,37 @@ +import { + Entity, + PrimaryGeneratedColumn, + OneToMany, + ManyToOne, + Column, +} from 'typeorm'; + +@Entity() +export class Vote { + @PrimaryGeneratedColumn({}) + id: number; + + @OneToMany(() => Answer, (answer) => answer.vote, { + cascade: true, + eager: true, + }) + answers: Answer[]; +} + +@Entity() +export class Answer { + @PrimaryGeneratedColumn({}) + id: number; + + @ManyToOne(() => Vote, (vote) => vote.answers, { + eager: false, + onDelete: 'CASCADE', + }) + vote: Promise; + + @Column() + question_id: string; + + @Column() + answer: string; +} diff --git a/src/vote/vote.module.ts b/src/vote/vote.module.ts new file mode 100644 index 0000000..a3ac3fb --- /dev/null +++ b/src/vote/vote.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { Answer, Vote } from './vote.entity'; +import { VoteController } from './vote.controller'; +import { VoteService } from './vote.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +@Module({ + imports: [TypeOrmModule.forFeature([Vote, Answer])], + controllers: [VoteController], + providers: [VoteService], +}) +export class VoteModule {} diff --git a/src/polls/polls.service.spec.ts b/src/vote/vote.service.spec.ts similarity index 56% rename from src/polls/polls.service.spec.ts rename to src/vote/vote.service.spec.ts index a4f1324..133f891 100644 --- a/src/polls/polls.service.spec.ts +++ b/src/vote/vote.service.spec.ts @@ -1,15 +1,15 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { PollsService } from './polls.service'; +import { VoteService } from './vote.service'; -describe('PollsService', () => { - let service: PollsService; +describe('VoteService', () => { + let service: VoteService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [PollsService], + providers: [VoteService], }).compile(); - service = module.get(PollsService); + service = module.get(VoteService); }); it('should be defined', () => { diff --git a/src/vote/vote.service.ts b/src/vote/vote.service.ts new file mode 100644 index 0000000..81a0361 --- /dev/null +++ b/src/vote/vote.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@nestjs/common'; +import { VoteCreateDTO } from './vote.dto'; +import { Vote } from './vote.entity'; +import { Repository } from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; + +interface IAnswer { + question_id: string; + answer: string; +} + +interface IVote { + answers: IAnswer[]; +} + +@Injectable() +export class VoteService { + constructor( + @InjectRepository(Vote) private voteRepository: Repository, + ) {} + async create(value: VoteCreateDTO) { + const vote = Object.entries(value).reduce( + (acc, [question_id, a]) => { + const answer = String(a); + acc.answers.push({ question_id, answer }); + return acc; + }, + { + answers: [], + }, + ); + const voteEntity = this.voteRepository.create(vote); + await this.voteRepository.save(voteEntity); + } + + async getResults() { + const all_votes = await this.voteRepository.find(); + const compiled_results: { [question: string]: { [vote: string]: number } } = + {}; + + for (const vote of all_votes) { + for (const answer of vote.answers) { + if (!(answer.question_id in compiled_results)) { + compiled_results[answer.question_id] = {}; + } + if (!(answer.answer in compiled_results[answer.question_id])) { + compiled_results[answer.question_id][answer.answer] = 0; + } + compiled_results[answer.question_id][answer.answer]++; + } + } + return compiled_results; + } +}