diff --git a/src/form/dto/create-form.dto.ts b/src/form/dto/create-form.dto.ts new file mode 100644 index 0000000..0a83d99 --- /dev/null +++ b/src/form/dto/create-form.dto.ts @@ -0,0 +1,31 @@ +import { IsString, IsOptional, IsUUID, ValidateNested, IsArray, IsEnum, IsDateString } from 'class-validator'; +import { Type } from 'class-transformer'; +import { BaseDto } from '../../_core/dto/base.dto'; +import { CreateAttachmentDto } from '../../attachment/dto/create_attachment.dto'; +import { CreateQuestionDto } from '../../question/dto/create-question.dto'; +import { CreateFormPageDto } from '../../formPage/dto/create-formPage.dto'; +import { CreateParticipantDto } from '../../participant/dto/create-participant.dto'; + +export class CreateFormDto extends BaseDto { + @IsString() + title: string; + + @IsString() + description: string; + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => CreateAttachmentDto) + @IsOptional() + attachments?: CreateAttachmentDto[]; + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => CreateFormPageDto) + pages: CreateFormPageDto[]; + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => CreateParticipantDto) + participants: CreateParticipantDto[]; +} \ No newline at end of file diff --git a/src/form/dto/update-form.dto.ts b/src/form/dto/update-form.dto.ts new file mode 100644 index 0000000..3eb426b --- /dev/null +++ b/src/form/dto/update-form.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateFormDto } from './create-form.dto'; + +export class UpdateFormDto extends PartialType(CreateFormDto) {} diff --git a/src/form/entity/form.entity.ts b/src/form/entity/form.entity.ts new file mode 100644 index 0000000..25ff6da --- /dev/null +++ b/src/form/entity/form.entity.ts @@ -0,0 +1,58 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Document } from 'mongoose'; +import { BaseEntity } from 'src/_core/entity/_base.entity'; +import * as mongoosePaginate from 'mongoose-paginate-v2'; +import { Attachment, AttachmentSchema } from '../../attachment/entity/attachment.entity'; +import { Option, OptionSchema } from '../../option/entity/option.entity'; +import { Answer, AnswerSchema } from '../../answer/entity/answer.entity'; +import { Question, QuestionSchema } from '../../question/entity/question.entity'; +import { Participant, ParticipantSchema } from '../../participant/entity/participant.entity'; +import { FormPage, FormPageSchema } from '../../formPage/entity/formPage.entity'; + +@Schema({ _id: false, id: false }) // Disable _id and virtual id +export class Form extends BaseEntity { + @Prop({ + type: String, + required: true, + }) + title: string; + + @Prop({ + type: String, + required: true, + }) + description: string; + + @Prop({ + type: [AttachmentSchema], + default: [], + }) + attachments: Attachment[]; + + @Prop({ + type: [FormPageSchema], + default: [], + }) + pages: FormPage[]; + + @Prop({ + type: [ParticipantSchema], + default: [], + }) + participants: Participant[]; +} + +export type FormDocument = Form & Document; +export const FormSchema = SchemaFactory.createForClass(Form); +FormSchema.plugin(mongoosePaginate); + +// Transform the output to remove the internal '_id' +FormSchema.set('toJSON', { + transform: (doc: FormDocument, ret: Form & { _id?: any }) => { + delete ret._id; + return ret; + }, +}); + + + diff --git a/src/form/form.controller.ts b/src/form/form.controller.ts new file mode 100644 index 0000000..d31fe07 --- /dev/null +++ b/src/form/form.controller.ts @@ -0,0 +1,47 @@ +import { Controller, Get, Post, Patch, Delete, Param, Body, UsePipes, ValidationPipe, Query, ParseUUIDPipe, HttpCode, HttpStatus,} from '@nestjs/common'; +import { FormService } from './form.service'; +import { CreateFormDto } from './dto/create-form.dto'; +import { UpdateFormDto } from './dto/update-form.dto'; +import { Form } from './entity/form.entity'; + +@Controller('forms') +export class FormController { + constructor(private readonly formService: FormService) {} + + @Post() + @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) + async create(@Body() body: CreateFormDto): Promise
{ + return this.formService.create(body); + } + + @Get('findAll') + async findAll( + @Query('page') page: string = '1', + @Query('limit') limit: string = '10', + ) { + // The service returns the full pagination object + return this.formService.findAll(parseInt(page, 10), parseInt(limit, 10)); + } + + @Get(':id') + async findOne( + @Param('id', new ParseUUIDPipe()) id: string, + ): Promise { + return this.formService.findById(id); + } + + @Patch(':id') + @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) + async update( + @Param('id', new ParseUUIDPipe()) id: string, + @Body() body: UpdateFormDto, + ): Promise { + return this.formService.update(id, body); + } + + @Delete(':id') + @HttpCode(HttpStatus.NO_CONTENT) + async remove(@Param('id', new ParseUUIDPipe()) id: string): Promise { + return this.formService.remove(id); + } +} diff --git a/src/form/form.module.ts b/src/form/form.module.ts new file mode 100644 index 0000000..bfd3395 --- /dev/null +++ b/src/form/form.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { FormService } from './form.service'; +import { FormController } from './form.controller'; +import { Form, FormSchema } from './entity/form.entity'; + +@Module({ + imports: [ + MongooseModule.forFeature([ + { name: Form.name, schema: FormSchema }, + ]), + ], + controllers: [FormController], + providers: [FormService], +}) +export class FormModule {} diff --git a/src/form/form.service.ts b/src/form/form.service.ts new file mode 100644 index 0000000..35f572f --- /dev/null +++ b/src/form/form.service.ts @@ -0,0 +1,82 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { Form, FormDocument } from './entity/form.entity'; +import { CreateFormDto } from './dto/create-form.dto'; +import { UpdateFormDto } from './dto/update-form.dto'; +import { PaginateModel } from '../participant/participant.service'; + +@Injectable() +export class FormService { + constructor( + @InjectModel(Form.name) + private readonly formModel: PaginateModel, + ) {} + + async create(data: CreateFormDto): Promise { + const form = new this.formModel({ + ...data, + id: data.id || undefined, // Let BaseEntity generate UUID if not provided + metadata: data.metadata || { entries: [] }, + }); + return form.save(); + } + + async findAll( + page = 1, + limit = 10, + ): Promise<{ + docs: Form[]; + totalDocs: number; + limit: number; + page: number; + totalPages: number; + hasNextPage: boolean; + hasPrevPage: boolean; + nextPage: number | null; + prevPage: number | null; + }> { + // Selects all fields from the Form entity for the response + return this.formModel.paginate( + {}, + { page, limit, lean: true }, + ); + } + + async findById(id: string): Promise { + const form = await this.formModel + .findOne({ id }) + .lean() + .exec(); + if (!form) { + throw new NotFoundException(`Form with ID "${id}" not found`); + } + return form; + } + + async update( + id: string, + data: UpdateFormDto, + ): Promise { + const updatedForm = await this.formModel + .findOneAndUpdate( + { id }, + { $set: { ...data, updatedAt: new Date() } }, + { new: true }, + ) + .lean() + .exec(); + + if (!updatedForm) { + throw new NotFoundException(`Form with ID "${id}" not found`); + } + return updatedForm; + } + + async remove(id: string): Promise { + const result = await this.formModel.deleteOne({ id }).exec(); + if (result.deletedCount === 0) { + throw new NotFoundException(`Form with ID "${id}" not found`); + } + } +}