diff --git a/src/_core/dto/base.dto.ts b/src/_core/dto/base.dto.ts index 51caa8e..4dadfa2 100644 --- a/src/_core/dto/base.dto.ts +++ b/src/_core/dto/base.dto.ts @@ -1,9 +1,24 @@ -import { IsString, IsEnum, IsOptional, IsUUID, ValidateNested } from 'class-validator'; +import { IsEnum, IsOptional, IsUUID, ValidateNested, IsArray, IsString } from 'class-validator'; import { Type } from 'class-transformer'; -import { MetadataDto } from './metadataEntry.dto'; + +export class MetadataEntryDto { + @IsString() + key: string; + + @IsString() + value: string; +} + +export class MetadataDto { + @IsArray() + @ValidateNested({ each: true }) + @Type(() => MetadataEntryDto) + @IsOptional() + entries?: { key: string; value: string }[]; +} export class BaseDto { - @IsUUID() + @IsUUID('4') @IsOptional() id?: string; @@ -15,4 +30,4 @@ export class BaseDto { @Type(() => MetadataDto) @IsOptional() metadata?: MetadataDto; -} \ No newline at end of file +} diff --git a/src/_core/dto/metadataEntry.dto.ts b/src/_core/dto/metadataEntry.dto.ts deleted file mode 100644 index 48d8881..0000000 --- a/src/_core/dto/metadataEntry.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { IsString, ValidateNested } from 'class-validator'; -import { Type } from 'class-transformer'; - -class MetadataEntryDto { - @IsString() - key: string; - - @IsString() - value: string; -} - -export class MetadataDto { - @ValidateNested({ each: true }) - @Type(() => MetadataEntryDto) - entries: MetadataEntryDto[]; -} diff --git a/src/_core/entity/_base.entity.ts b/src/_core/entity/_base.entity.ts index 0b5104b..09e5c49 100644 --- a/src/_core/entity/_base.entity.ts +++ b/src/_core/entity/_base.entity.ts @@ -1,7 +1,11 @@ import { Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn, BeforeInsert } from 'typeorm'; -import { Metadata } from './_metadata.entity'; import { v4 as uuidv4 } from 'uuid'; +export class Metadata { + @Column({ type: 'jsonb', nullable: true, default: () => "'[]'" }) + entries?: { key: string; value: string }[]; +} + export abstract class BaseEntity { @PrimaryGeneratedColumn('uuid') id: string; @@ -13,7 +17,7 @@ export abstract class BaseEntity { updatedAt: Date; @Column(() => Metadata) - metadata: Metadata = new Metadata(); + metadata?: Metadata; @Column({ type: 'enum', @@ -28,5 +32,4 @@ export abstract class BaseEntity { this.id = uuidv4(); } } - -} \ No newline at end of file +} diff --git a/src/_core/entity/_metadata.entity.ts b/src/_core/entity/_metadata.entity.ts deleted file mode 100644 index 8fcf160..0000000 --- a/src/_core/entity/_metadata.entity.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Column, Entity } from 'typeorm'; - -@Entity({ name: 'metadata' }) -export class Metadata { - @Column({ - type: 'uuid', - primary: true, - default: () => 'uuid_generate_v4()', - }) - id?: string; - - @Column({ - type: 'jsonb', - default: () => "'[]'", - }) - entries?: { key: string; value: string }[]; -} \ No newline at end of file diff --git a/src/answer/answer.service.ts b/src/answer/answer.service.ts index baf32ec..1695e78 100644 --- a/src/answer/answer.service.ts +++ b/src/answer/answer.service.ts @@ -14,21 +14,14 @@ export class AnswerService { async create(data: CreateAnswerDto): Promise { try { - const answer = this.answerRepository.create({ - ...data, - metadata: data.metadata || { entries: [] }, - status: data.status || 'active', - }); + const answer = this.answerRepository.create({ ...data }); return await this.answerRepository.save(answer); } catch (error) { throw new Error(`Failed to create answer: ${error.message}`); } } - async findAll( - page = 1, - limit = 10, - ): Promise<{ + async findAll( page = 1, limit = 10 ): Promise<{ docs: Answer[]; totalDocs: number; limit: number; diff --git a/src/answer/dto/create-answer.dto.ts b/src/answer/dto/create-answer.dto.ts index 539c44c..b8315d6 100644 --- a/src/answer/dto/create-answer.dto.ts +++ b/src/answer/dto/create-answer.dto.ts @@ -2,10 +2,10 @@ import { IsString, IsUUID } from 'class-validator'; import { BaseDto } from '../../_core/dto/base.dto'; export class CreateAnswerDto extends BaseDto { - @IsUUID() + @IsUUID('4') participantId: string; - @IsUUID() + @IsUUID('4') questionId: string; @IsString() diff --git a/src/attachment/dto/create_attachment.dto.ts b/src/attachment/dto/create_attachment.dto.ts index dec3faf..6c1eacc 100644 --- a/src/attachment/dto/create_attachment.dto.ts +++ b/src/attachment/dto/create_attachment.dto.ts @@ -1,5 +1,4 @@ -import { IsEnum, IsOptional, IsString, IsUrl, IsUUID, ValidateNested } from 'class-validator'; -import { Type } from 'class-transformer'; +import { IsEnum, IsOptional, IsString, IsUrl, IsUUID } from 'class-validator'; import { BaseDto } from '../../_core/dto/base.dto'; export class CreateAttachmentDto extends BaseDto { @@ -15,6 +14,6 @@ export class CreateAttachmentDto extends BaseDto { @IsUrl() storageUrl: string; - @IsUUID() + @IsUUID('4') ownerId: string; } \ No newline at end of file diff --git a/src/config/database.config.ts b/src/config/database.config.ts index 717d9f1..7890db5 100644 --- a/src/config/database.config.ts +++ b/src/config/database.config.ts @@ -1,7 +1,6 @@ // src/config/database.config.ts import { TypeOrmModuleOptions } from '@nestjs/typeorm'; import { Participant } from '../participant/entity/participant.entity'; -import { Metadata } from '../_core/entity/_metadata.entity'; import { Form } from '../form/entity/form.entity'; import { Realm } from '../realm/entity/realm.entity'; import { Question } from '../question/entity/question.entity'; @@ -12,7 +11,7 @@ import { FormSection } from '../formSection/entity/formSection.entity'; import { FormPage } from '../formPage/entity/formPage.entity'; import { FormResult } from '../formResult/entity/formResult.entity'; import { ParticipantGroup } from '../participantGroup/entity/participantGroup.entity'; -import { BaseEntity } from '../_core/entity/_base.entity'; +import { BaseEntity, Metadata } from '../_core/entity/_base.entity'; export const typeOrmConfig: TypeOrmModuleOptions = { type: 'postgres', diff --git a/src/formResult/dto/create-formResult.dto.ts b/src/formResult/dto/create-formResult.dto.ts index ab3f9f1..4026639 100644 --- a/src/formResult/dto/create-formResult.dto.ts +++ b/src/formResult/dto/create-formResult.dto.ts @@ -17,7 +17,7 @@ class CreateOpinionDto { @Type(() => CreateOptionsDto) options: CreateOptionsDto; - @IsUUID() + @IsUUID('4') questionId: string; } diff --git a/src/formResult/entity/formResult.entity.ts b/src/formResult/entity/formResult.entity.ts index 5e5658e..f8a0e32 100644 --- a/src/formResult/entity/formResult.entity.ts +++ b/src/formResult/entity/formResult.entity.ts @@ -26,8 +26,8 @@ export class Opinion { @Entity() export class FormResult extends BaseEntity { - @OneToOne(() => Form, form => form.formResult, { /*cascade: true, eager: true */}) - form: Form; + @Column({ type: 'uuid' }) + formId: string; @Column({ type: 'int', default: 0 }) numParticipants: number; diff --git a/src/formResult/formResult.controller.ts b/src/formResult/formResult.controller.ts index 315706a..daac7a8 100644 --- a/src/formResult/formResult.controller.ts +++ b/src/formResult/formResult.controller.ts @@ -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('form/:formId/statistics') + // async getFormStatistics( + // @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 f19e57b..5e2e5d8 100644 --- a/src/formResult/formResult.service.ts +++ b/src/formResult/formResult.service.ts @@ -94,90 +94,90 @@ 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 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); + // } } diff --git a/src/realm/realm.service.ts b/src/realm/realm.service.ts index 1552fe1..bfce6c0 100644 --- a/src/realm/realm.service.ts +++ b/src/realm/realm.service.ts @@ -49,7 +49,7 @@ export class RealmService { displayName: data.title + ' Owner', // Default displayName role: 'admin', // Default role for owner userId: decodedToken.sub, - metadata: data.metadata ? { entries: data.metadata.entries } : { entries: [] }, + metadata: data.metadata , // status: 'active', }); participant = await this.participantRepo.save(participant); @@ -58,7 +58,7 @@ export class RealmService { // Create the realm with the participant as the owner const realm = this.realmRepo.create({ ...data, - owner: participant, // Set owner to the participant with userId = sub + ownerId: participant.userId, // Set owner to the participant with userId = sub }); return await this.realmRepo.save(realm);