add invite code support

This commit is contained in:
2025-06-01 13:07:45 +02:00
parent 7383ce6977
commit 495ac35937
5 changed files with 85 additions and 12 deletions
+2 -4
View File
@@ -3,9 +3,7 @@ import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { VoteController } from './vote/vote.controller';
import { VoteService } from './vote/vote.service';
import { Answer, Vote } from './vote/vote.entity';
import { Answer, InviteCode, Vote } from './vote/vote.entity';
import { VoteModule } from './vote/vote.module';
void ConfigModule.forRoot({
@@ -24,7 +22,7 @@ const configService = new ConfigService();
username: configService.get('PG_USER'),
database: configService.get('PG_DATABASE'),
synchronize: configService.get('DEV_MODE') == 'true',
entities: [Vote, Answer],
entities: [Vote, Answer, InviteCode],
}),
VoteModule,
],
+33 -4
View File
@@ -1,4 +1,13 @@
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import {
Body,
Controller,
Get,
HttpException,
HttpStatus,
Param,
ParseUUIDPipe,
Post,
} from '@nestjs/common';
import { VoteCreateDTO } from './vote.dto';
import { VoteService } from './vote.service';
import { ApiOperation } from '@nestjs/swagger';
@@ -7,18 +16,38 @@ import { ApiOperation } from '@nestjs/swagger';
export class VoteController {
constructor(private readonly voteService: VoteService) {}
@Get('check/:accessCode')
@ApiOperation({ summary: 'Checks if a code has been used' })
async check(@Param('accessCode', new ParseUUIDPipe()) accessCode: string) {
const value = await this.voteService.check(accessCode);
if (value === null) {
throw new HttpException('Invalid accessCode', HttpStatus.NOT_FOUND);
}
if (value === false) {
throw new HttpException('AccessCode expired', HttpStatus.FORBIDDEN);
}
return;
}
@Post('vote/:accessCode')
@ApiOperation({ summary: 'Vote for a poll' })
async vote(
@Param('accessCode') accessCode: string,
@Param('accessCode', new ParseUUIDPipe()) accessCode: string,
@Body() answer: VoteCreateDTO,
) {
await this.voteService.create(answer);
const code = await this.voteService.check(accessCode);
if (code === null) {
throw new HttpException('Invalid accessCode', HttpStatus.NOT_FOUND);
}
if (code === false) {
throw new HttpException('AccessCode expired', HttpStatus.FORBIDDEN);
}
await this.voteService.create(answer, code);
}
@Get('results/:accessCode')
@ApiOperation({ summary: 'Get results' })
async results(@Param('accessCode') accessCode: string) {
async results(@Param('accessCode', new ParseUUIDPipe()) accessCode: string) {
return await this.voteService.getResults();
}
}
+28
View File
@@ -4,6 +4,8 @@ import {
OneToMany,
ManyToOne,
Column,
OneToOne,
Generated,
} from 'typeorm';
@Entity()
@@ -16,6 +18,13 @@ export class Vote {
eager: true,
})
answers: Answer[];
@OneToOne(() => InviteCode, (code) => code.vote, {
eager: false,
onDelete: 'CASCADE',
nullable: false,
})
code: Promise<InviteCode>;
}
@Entity()
@@ -35,3 +44,22 @@ export class Answer {
@Column()
answer: string;
}
@Entity()
export class InviteCode {
@PrimaryGeneratedColumn({})
id: number;
@OneToOne(() => Vote, (vote) => vote.code, {
eager: false,
onDelete: 'CASCADE',
})
vote: Promise<Vote>;
@Column({ default: 1 })
usages_left: number;
@Column()
@Generated('uuid')
value: string;
}
+2 -2
View File
@@ -1,11 +1,11 @@
import { Module } from '@nestjs/common';
import { Answer, Vote } from './vote.entity';
import { InviteCode, 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])],
imports: [TypeOrmModule.forFeature([Vote, InviteCode])],
controllers: [VoteController],
providers: [VoteService],
})
+20 -2
View File
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { VoteCreateDTO } from './vote.dto';
import { Vote } from './vote.entity';
import { InviteCode, Vote } from './vote.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
@@ -17,8 +17,13 @@ interface IVote {
export class VoteService {
constructor(
@InjectRepository(Vote) private voteRepository: Repository<Vote>,
@InjectRepository(InviteCode)
private inviteCodeRepository: Repository<InviteCode>,
) {}
async create(value: VoteCreateDTO) {
async create(value: VoteCreateDTO, accessCode: InviteCode) {
accessCode.usages_left--;
await this.inviteCodeRepository.save(accessCode);
const vote = Object.entries(value).reduce<IVote>(
(acc, [question_id, a]) => {
const answer = String(a);
@@ -51,4 +56,17 @@ export class VoteService {
}
return compiled_results;
}
async check(inviteCode: string) {
const code = await this.inviteCodeRepository.findOneBy({
value: inviteCode,
});
if (!code) {
return null;
}
if (code.usages_left > 0) {
return code;
}
return false;
}
}