first version
This commit is contained in:
Vendored
+20
@@ -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": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
],
|
||||||
|
"program": "${workspaceFolder}/src/main.ts",
|
||||||
|
"preLaunchTask": "npm: build",
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+6
-18
@@ -3,14 +3,10 @@ import { AppController } from './app.controller';
|
|||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
import { PollsModule } from './polls/polls.module';
|
import { VoteController } from './vote/vote.controller';
|
||||||
import { Answer } from './polls/entities/answer.entity';
|
import { VoteService } from './vote/vote.service';
|
||||||
import { Choice } from './polls/entities/choice.entity';
|
import { Answer, Vote } from './vote/vote.entity';
|
||||||
import { InviteCode } from './polls/entities/inviteCode.entity';
|
import { VoteModule } from './vote/vote.module';
|
||||||
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';
|
|
||||||
|
|
||||||
void ConfigModule.forRoot({
|
void ConfigModule.forRoot({
|
||||||
envFilePath: '.env',
|
envFilePath: '.env',
|
||||||
@@ -28,17 +24,9 @@ const configService = new ConfigService();
|
|||||||
username: configService.get('PG_USER'),
|
username: configService.get('PG_USER'),
|
||||||
database: configService.get('PG_DATABASE'),
|
database: configService.get('PG_DATABASE'),
|
||||||
synchronize: configService.get('DEV_MODE') == 'true',
|
synchronize: configService.get('DEV_MODE') == 'true',
|
||||||
entities: [
|
entities: [Vote, Answer],
|
||||||
Answer,
|
|
||||||
Choice,
|
|
||||||
InviteCode,
|
|
||||||
Poll,
|
|
||||||
Question,
|
|
||||||
QuestionOption,
|
|
||||||
User,
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
PollsModule,
|
VoteModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ async function bootstrap() {
|
|||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
||||||
app.useGlobalPipes(new ValidationPipe());
|
app.useGlobalPipes(new ValidationPipe());
|
||||||
|
app.enableCors({ origin: '*' });
|
||||||
|
|
||||||
const config = new DocumentBuilder()
|
const config = new DocumentBuilder()
|
||||||
.setTitle('Vote au jugement majoritaire')
|
.setTitle('Vote au jugement majoritaire')
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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[];
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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[];
|
|
||||||
}
|
|
||||||
@@ -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[];
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
|
|
||||||
|
|
||||||
@Entity()
|
|
||||||
export class User {
|
|
||||||
@PrimaryGeneratedColumn({})
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
username: string;
|
|
||||||
}
|
|
||||||
@@ -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<PollReadAdminDTO> {
|
|
||||||
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<PollReadAdminDTO> {
|
|
||||||
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<PollReadDTO> {
|
|
||||||
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<PollReadAdminDTO> {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 {}
|
|
||||||
@@ -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<Poll>,
|
|
||||||
@InjectRepository(InviteCode)
|
|
||||||
private inviteRepository: Repository<InviteCode>,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<Question>,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
get(poll: Poll, questionId: number) {
|
|
||||||
const question = poll.questions.find((q) => q.id == questionId);
|
|
||||||
return question;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>(VoteController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export class VoteCreateDTO {
|
||||||
|
'1': string;
|
||||||
|
'2.1': string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class VoteResultsDTO {
|
||||||
|
'1': number[];
|
||||||
|
'2': number[];
|
||||||
|
}
|
||||||
@@ -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<Vote>;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
question_id: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
answer: string;
|
||||||
|
}
|
||||||
@@ -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 {}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { PollsService } from './polls.service';
|
import { VoteService } from './vote.service';
|
||||||
|
|
||||||
describe('PollsService', () => {
|
describe('VoteService', () => {
|
||||||
let service: PollsService;
|
let service: VoteService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [PollsService],
|
providers: [VoteService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<PollsService>(PollsService);
|
service = module.get<VoteService>(VoteService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
@@ -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<Vote>,
|
||||||
|
) {}
|
||||||
|
async create(value: VoteCreateDTO) {
|
||||||
|
const vote = Object.entries(value).reduce<IVote>(
|
||||||
|
(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user