From f3d0659c39feaaf52d92980cb5d54646639fc6d5 Mon Sep 17 00:00:00 2001 From: OkaykOrhmn Date: Wed, 23 Jul 2025 16:45:04 +0330 Subject: [PATCH] added some token usage, has errors --- src/answer/answer.service.ts | 18 ++- src/attachment/attachment.service.ts | 50 ++----- src/formResult/formResult.controller.ts | 14 +- src/formResult/formResult.service.ts | 166 ++++++++++++------------ 4 files changed, 108 insertions(+), 140 deletions(-) diff --git a/src/answer/answer.service.ts b/src/answer/answer.service.ts index 1695e78..ba762d5 100644 --- a/src/answer/answer.service.ts +++ b/src/answer/answer.service.ts @@ -1,9 +1,11 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; +import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Answer } from './entity/answer.entity'; import { CreateAnswerDto } from './dto/create-answer.dto'; import { UpdateAnswerDto } from './dto/update-answer.dto'; +import { CreateAttachmentDto } from '../attachment/dto/create_attachment.dto'; +import { Attachment } from '../attachment/entity/attachment.entity'; @Injectable() export class AnswerService { @@ -12,13 +14,15 @@ export class AnswerService { private readonly answerRepository: Repository, ) {} - async create(data: CreateAnswerDto): Promise { - try { - const answer = this.answerRepository.create({ ...data }); - return await this.answerRepository.save(answer); - } catch (error) { - throw new Error(`Failed to create answer: ${error.message}`); + async create(data: CreateAnswerDto, token: any): Promise { + if (!token || !token.sub) { + throw new InternalServerErrorException('Failed to extract sub from token'); } + const answer = this.answerRepository.create({ + ...data, + participantId: token.sub, // token.sub is userId, but we want participantId ?! + }); + return await this.answerRepository.save(answer); } async findAll( page = 1, limit = 10 ): Promise<{ diff --git a/src/attachment/attachment.service.ts b/src/attachment/attachment.service.ts index ba192ec..fdd0136 100644 --- a/src/attachment/attachment.service.ts +++ b/src/attachment/attachment.service.ts @@ -14,47 +14,17 @@ export class AttachmentService { private readonly attachmentRepository: Repository, ) {} - async create(data: CreateAttachmentDto): Promise { - try { - // Here we send a sample request, in final implementation, there'll be no need for that, we'll get the decoded token - const authResponse = await axios.post( - 'https://auth.didvan.com/realms/didvan/protocol/openid-connect/token', - new URLSearchParams({ - client_id: 'didvan-app', - username: 'bob', - password: 'developer_password', - grant_type: 'password', - }), - { - headers: { - 'content-type': 'application/x-www-form-urlencoded', - }, - }, - ); - - // Decode the access token to extract the 'sub' claim - const token = authResponse.data.access_token; - const decodedToken: any = decode(token); - if (!decodedToken || !decodedToken.sub) { - throw new InternalServerErrorException('Failed to decode token or extract sub'); - } - - - // Create the attachment with ownerId set to sub - const attachment = this.attachmentRepository.create({ - ...data, - ownerId: decodedToken.sub, // Set ownerId to the token's sub - metadata: data.metadata - ? { entries: data.metadata.entries } - : { entries: [] }, - status: data.status || 'active', - }); - - // Save the attachment to the database - return await this.attachmentRepository.save(attachment); - } catch (error) { - throw new InternalServerErrorException(`Failed to create attachment: ${error.message}`); + async create(data: CreateAttachmentDto, token: any): Promise { + // Extract sub from decoded token + if (!token || !token.sub) { + throw new InternalServerErrorException('Failed to extract sub from token'); } + // Create the attachment with ownerId set to token.sub + const attachment = this.attachmentRepository.create({ + ...data, + ownerId: token.sub, // Set ownerId to the token's sub + }); + return await this.attachmentRepository.save(attachment); } async findAll( diff --git a/src/formResult/formResult.controller.ts b/src/formResult/formResult.controller.ts index daac7a8..60b8987 100644 --- a/src/formResult/formResult.controller.ts +++ b/src/formResult/formResult.controller.ts @@ -23,7 +23,7 @@ export class FormResultController { } @Get(':id') - async findOne( + async findOne( // just gets the data in database @Param('id', new ParseUUIDPipe()) id: string, ): Promise { return this.formResultService.findById(id); @@ -44,10 +44,10 @@ export class FormResultController { return this.formResultService.remove(id); } - // @Get('form/:formId/statistics') - // async getFormStatistics( - // @Param('formId', new ParseUUIDPipe()) formId: string, - // ): Promise { - // return this.formResultService.getFormStatistics(formId); - // } + @Get(':id/refresh') + async getFormStatistics( // updates data in database and then returns + @Param('formId', new ParseUUIDPipe()) formId: string, + ): Promise { + return this.formResultService.getFormStatistics(formId); + } } \ No newline at end of file diff --git a/src/formResult/formResult.service.ts b/src/formResult/formResult.service.ts index 5e2e5d8..9543b82 100644 --- a/src/formResult/formResult.service.ts +++ b/src/formResult/formResult.service.ts @@ -1,12 +1,14 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; import { FormResult } from './entity/formResult.entity'; import { CreateFormResultDto } from './dto/create-formResult.dto'; import { UpdateFormResultDto } from './dto/update-formResult.dto'; import { Answer } from 'src/answer/entity/answer.entity'; import { Question } from '../question/entity/question.entity'; import { Form } from '../form/entity/form.entity'; +import { FormSection } from '../formSection/entity/formSection.entity'; +import { FormPage } from '../formPage/entity/formPage.entity'; +import { Repository, In } from 'typeorm'; @Injectable() export class FormResultService { @@ -19,6 +21,15 @@ export class FormResultService { private readonly questionRepository: Repository, @InjectRepository(Answer) private readonly answerRepository: Repository, + @InjectRepository(Form) + @InjectRepository(FormPage) + private readonly formPageRepository: Repository, + @InjectRepository(FormSection) + private readonly formSectionRepository: Repository, + @InjectRepository(Question) + @InjectRepository(Answer) + @InjectRepository(FormResult) + private readonly formResultRepository: Repository, ) {} async create(data: CreateFormResultDto): Promise { @@ -94,90 +105,73 @@ export class FormResultService { throw new NotFoundException(`FormResult with ID "${id}" not found`); } } - // - // async getFormStatistics(formId: string): Promise { - // // 1. Find the Form entity - // const form = await this.formRepository.findOne({ where: { id: formId } }); - // if (!form) { - // throw new NotFoundException(`Form with ID "${formId}" not found`); - // } - // - // // 2. Compute numQuestions - // const numQuestions = await this.questionRepository.count({ - // where: { - // formSection: { - // formPage: { - // form: { id: formId }, - // }, - // }, - // }, - // }); - // - // // 3. Compute numParticipants - // const numParticipantsResult = await this.answerRepository - // .createQueryBuilder('answer') - // .select('COUNT(DISTINCT answer.participantId)', 'count') - // .innerJoin('answer.question', 'question') - // .innerJoin('question.formSection', 'formSection') - // .innerJoin('formSection.formPage', 'formPage') - // .where('formPage.formId = :formId', { formId }) - // .getRawOne(); - // const numParticipants = numParticipantsResult ? Number(numParticipantsResult.count) : 0; - // - // // 4. Compute numAnswers - // const numAnswers = await this.answerRepository.count({ - // where: { - // question: { - // formSection: { - // formPage: { - // form: { id: formId }, - // }, - // }, - // }, - // }, - // }); - // - // // 5. Compute numCompleteParticipants - // const subQuery = this.answerRepository - // .createQueryBuilder('answer') - // .select('answer.participantId') - // .innerJoin('answer.question', 'question') - // .innerJoin('question.formSection', 'formSection') - // .innerJoin('formSection.formPage', 'formPage') - // .where('formPage.formId = :formId', { formId }) - // .groupBy('answer.participantId') - // .having('COUNT(DISTINCT answer.questionId) = :numQuestions', { numQuestions }); - // - // const numCompleteParticipantsResult = await this.answerRepository.manager - // .createQueryBuilder() - // .select('COUNT(*)', 'count') - // .from(`(${subQuery.getQuery()})`, 'subquery') - // .setParameters(subQuery.getParameters()) - // .getRawOne(); - // const numCompleteParticipants = numCompleteParticipantsResult ? Number(numCompleteParticipantsResult.count) : 0; - // - // // 6. Find or create FormResult for the Form - // let formResult = await this.formResultRepo.findOne({ where: { form: { id: formId } }, relations: ['form'] }); - // if (!formResult) { - // formResult = this.formResultRepo.create({ - // form, // Link to the Form entity - // numQuestions, - // numParticipants, - // numAnswers, - // numComplete: numCompleteParticipants, - // opinions: [], // Initialize as empty or compute if needed - // status: 'active', - // }); - // } else { - // formResult.numQuestions = numQuestions; - // formResult.numParticipants = numParticipants; - // formResult.numAnswers = numAnswers; - // formResult.numComplete = numCompleteParticipants; - // // Preserve existing opinions or update if needed - // } - // - // // 7. Save and return the FormResult - // return await this.formResultRepo.save(formResult); - // } + + async getFormStatistics(formId: string): Promise { + // 1. Find the Form entity + const form = await this.formRepository.findOne({ where: { id: formId } }); + if (!form) { + throw new NotFoundException(`Form with ID "${formId}" not found`); + } + + // 2. Compute numQuestions + const pageIds = form.pageIds || []; + const formPages = await this.formPageRepository.find({ where: { id: In(pageIds) } }); + const formSectionIds = formPages.flatMap(page => page.formSectionIds || []); + const formSections = await this.formSectionRepository.find({ where: { id: In(formSectionIds) } }); + const questionIds = formSections.flatMap(section => section.questionIds || []); + const numQuestions = questionIds.length; + + // 3. Compute numParticipants (distinct participantId from answers) + const numParticipantsResult = await this.answerRepository + .createQueryBuilder('answer') + .select('COUNT(DISTINCT answer.participantId)', 'count') + .where('answer.questionId IN (:...questionIds)', { questionIds: questionIds.length ? questionIds : ['none'] }) // Handle empty questionIds + .getRawOne(); + const numParticipants = numParticipantsResult ? Number(numParticipantsResult.count) : 0; + + // 4. Compute numAnswers + const numAnswers = await this.answerRepository.count({ + where: { questionId: In(questionIds.length ? questionIds : ['none']) }, // Handle empty questionIds + }); + + // 5. Compute numCompleteParticipants (participants who answered all questions) + const subQuery = this.answerRepository + .createQueryBuilder('answer') + .select('answer.participantId') + .where('answer.questionId IN (:...questionIds)', { questionIds: questionIds.length ? questionIds : ['none'] }) // Handle empty questionIds + .groupBy('answer.participantId') + .having('COUNT(DISTINCT answer.questionId) = :numQuestions', { numQuestions: numQuestions || 1 }); // Avoid division by zero + + const numCompleteParticipantsResult = await this.answerRepository.manager + .createQueryBuilder() + .select('COUNT(*)', 'count') + .from(`(${subQuery.getQuery()})`, 'subquery') + .setParameters(subQuery.getParameters()) + .getRawOne(); + const numCompleteParticipants = numCompleteParticipantsResult ? Number(numCompleteParticipantsResult.count) : 0; + + // 6. Find or create FormResult for the Form + let formResult = await this.formResultRepository.findOne({ where: { formId } }); + if (!formResult) { + formResult = this.formResultRepository.create({ + formId, // Link to Form via formId + numQuestions, + numParticipants, + numAnswers, + numComplete: numCompleteParticipants, + opinions: [], // Initialize as empty or compute if needed + status: 'active', + }); + } else { + formResult.numQuestions = numQuestions; + formResult.numParticipants = numParticipants; + formResult.numAnswers = numAnswers; + formResult.numComplete = numCompleteParticipants; + // Preserve existing opinions or update if needed + } + + // 7. Save and return the FormResult + return await this.formResultRepository.save(formResult); + } }