diff --git a/backend/src/quizzes/entities/quiz.entity.ts b/backend/src/quizzes/entities/quiz.entity.ts index cc8685d..a0c1ea6 100644 --- a/backend/src/quizzes/entities/quiz.entity.ts +++ b/backend/src/quizzes/entities/quiz.entity.ts @@ -32,12 +32,12 @@ export class Quiz { @Column() passingScore: number; - @Column() - timeLimit: number; - - @Column() + @Column({ type: 'int', default: 1 }) maxAttempts: number; + @Column({ type: 'int', nullable: true }) + timeLimit: number; + @OneToMany(() => QuizQuestion, (quizQuestion) => quizQuestion.quiz) questions: QuizQuestion[]; diff --git a/backend/src/quizzes/quizzes.service.ts b/backend/src/quizzes/quizzes.service.ts index f221ccd..acb5f47 100644 --- a/backend/src/quizzes/quizzes.service.ts +++ b/backend/src/quizzes/quizzes.service.ts @@ -1,26 +1,129 @@ -import { Injectable } from '@nestjs/common'; +import { + Injectable, + ForbiddenException, + NotFoundException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Quiz } from './entities/quiz.entity'; import { CreateQuizDto } from './dto/create-quiz.dto'; import { UpdateQuizDto } from './dto/update-quiz.dto'; +import { SubmitQuizAttemptDto } from './dto/submit-quiz-attempt.dto'; +import { QuizAttempt } from './entities/quiz-attempt.entity'; @Injectable() export class QuizzesService { + constructor( + @InjectRepository(Quiz) + private readonly quizRepo: Repository, + + @InjectRepository(QuizAttempt) + private readonly submissionRepo: Repository, + ) {} + + // Create a new quiz create(createQuizDto: CreateQuizDto) { - return 'This action adds a new quiz'; + const quiz = this.quizRepo.create(createQuizDto); + return this.quizRepo.save(quiz); } + // Get all quizzes findAll() { - return `This action returns all quizzes`; + return this.quizRepo.find(); + } + + // Get a single quiz + async findOne(id: number) { + const quiz = await this.quizRepo.findOne({ where: { id } }); + if (!quiz) throw new NotFoundException('Quiz not found'); + return quiz; + } + + // Update a quiz + async update(id: number, updateQuizDto: UpdateQuizDto) { + await this.quizRepo.update(id, updateQuizDto); + return this.findOne(id); + } + + // Delete a quiz + async remove(id: number) { + await this.quizRepo.delete(id); + return { message: 'Quiz deleted' }; } - findOne(id: number) { - return `This action returns a #${id} quiz`; + // Validate user's quiz submission + async validateQuizSubmission( + userId: string, + quizId: string | number, + submissionId?: string, + ) { + const quiz = await this.quizRepo.findOne({ where: { id: Number(quizId) } }); + + if (!quiz) throw new NotFoundException('Quiz not found'); + + const attempts = await this.submissionRepo.count({ + where: { userId: String(userId), quizId: String(quizId) }, + }); + if (attempts >= quiz.maxAttempts) { + throw new ForbiddenException('Max attempts exceeded'); + } + + if (submissionId) { + const submission = await this.submissionRepo.findOne({ + where: { id: submissionId }, + }); + if (!submission) throw new NotFoundException('Submission not found'); + + const elapsed = + (new Date().getTime() - new Date(submission.startTime).getTime()) / + 1000; + if (quiz.timeLimit && elapsed > quiz.timeLimit) { + throw new ForbiddenException('Time limit exceeded'); + } + } } - update(id: number, updateQuizDto: UpdateQuizDto) { - return `This action updates a #${id} quiz`; + // Handle quiz submission + async submitQuizAttempt(submissionId: string, dto: SubmitQuizAttemptDto) { + const submission = await this.submissionRepo.findOne({ + where: { id: submissionId }, + relations: ['quiz'], + }); + + if (!submission) throw new NotFoundException('Submission not found'); + + const now = new Date(); + const elapsed = + (now.getTime() - new Date(submission.startTime).getTime()) / 1000; + + if (submission.quiz.timeLimit && elapsed > submission.quiz.timeLimit) { + throw new ForbiddenException('Time limit exceeded'); + } + + submission.answers = dto.answers; + submission.endTime = now; + + return this.submissionRepo.save(submission); } - remove(id: number) { - return `This action removes a #${id} quiz`; + async startQuizAttempt(userId: string, quizId: string) { + const quiz = await this.quizRepo.findOne({ where: { id: Number(quizId) } }); + if (!quiz) throw new NotFoundException('Quiz not found'); + + const attempts = await this.submissionRepo.count({ + where: { userId, quizId }, + }); + if (attempts >= quiz.maxAttempts) { + throw new ForbiddenException('Max attempts exceeded'); + } + + const submission = this.submissionRepo.create({ + userId, + quizId, + attemptNumber: attempts + 1, + startTime: new Date(), + }); + + return this.submissionRepo.save(submission); } }