option folder is not tested and revised

This commit is contained in:
OkaykOrhmn 2025-07-13 17:16:31 +03:30
parent 11971398cb
commit bdeef357ba
10 changed files with 165 additions and 33 deletions

View File

@ -4,6 +4,7 @@ import { MongooseModule } from '@nestjs/mongoose';
import { AppController } from './app.controller'; import { AppController } from './app.controller';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { ParticipantModule } from './participant/participant.module'; import { ParticipantModule } from './participant/participant.module';
import { QuestionModule } from './question/question.module';
// import { TestModule } from './test/test.module'; // import { TestModule } from './test/test.module';
@Module({ @Module({
@ -13,6 +14,7 @@ import { ParticipantModule } from './participant/participant.module';
}), }),
MongooseModule.forRoot(process.env.MONGODB_URI || 'mongodb://localhost:27017/db'), MongooseModule.forRoot(process.env.MONGODB_URI || 'mongodb://localhost:27017/db'),
ParticipantModule, ParticipantModule,
QuestionModule
], ],
controllers: [AppController], controllers: [AppController],
providers: [AppService], providers: [AppService],

View File

@ -18,9 +18,10 @@ export class Attachment extends BaseEntity {
@Prop({ @Prop({
type: String, type: String,
enum: ['png', 'pdf', 'csv', 'jpg'],
required: true, required: true,
}) })
fileType: string; fileType: 'png' | 'pdf' | 'csv' | 'jpg';
@Prop({ @Prop({
type: String, type: String,

View File

@ -0,0 +1,26 @@
import { IsString, IsOptional, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
class MetadataEntryDto {
@IsString()
key: string;
@IsString()
value: string;
}
class MetadataDto {
@ValidateNested({ each: true })
@Type(() => MetadataEntryDto)
entries: MetadataEntryDto[];
}
export class CreateOptionDto {
@IsString()
text: string;
@ValidateNested()
@Type(() => MetadataDto)
@IsOptional()
metadata?: MetadataDto;
}

View File

@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateOptionDto } from './create-option.dto';
export class UpdateOptionDto extends PartialType(CreateOptionDto) {}

View File

@ -0,0 +1,42 @@
import { Controller, Get, Post, Patch, Delete, Param, Body, UsePipes, ValidationPipe } from '@nestjs/common';
import { ParseUUIDPipe } from '@nestjs/common';
import { OptionService } from './option.service';
import { CreateOptionDto } from './dto/create-option.dto';
import { UpdateOptionDto } from './dto/update-option.dto';
import { Option } from './entity/option.entity';
import { Paginate, Paginated, PaginateQuery } from 'nestjs-paginate';
@Controller('options')
export class OptionController {
constructor(private readonly optionService: OptionService) {}
@Post()
@UsePipes(new ValidationPipe({ transform: true }))
async create(@Body() body: CreateOptionDto): Promise<Option> {
return this.optionService.create(body);
}
@Get()
async findAll(@Paginate() query: PaginateQuery): Promise<Paginated<Option>> {
return this.optionService.findAll(query);
}
@Get(':id')
async findOne(@Param('id', new ParseUUIDPipe()) id: string): Promise<Option | null> {
return this.optionService.findById(id);
}
@Patch(':id')
@UsePipes(new ValidationPipe({ transform: true }))
async update(
@Param('id', new ParseUUIDPipe()) id: string,
@Body() body: UpdateOptionDto,
): Promise<Option | null> {
return this.optionService.update(id, body);
}
@Delete(':id')
async remove(@Param('id', new ParseUUIDPipe()) id: string): Promise<void> {
return this.optionService.remove(id);
}
}

View File

@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { OptionService } from './option.service';
import { OptionController } from './option.controller';
import { Option, OptionSchema } from './entity/option.entity';
@Module({
imports: [
MongooseModule.forFeature([{ name: Option.name, schema: OptionSchema }]),
],
controllers: [OptionController],
providers: [OptionService],
})
export class OptionModule {}

View File

@ -0,0 +1,70 @@
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { CreateOptionDto } from './dto/create-option.dto';
import { UpdateOptionDto } from './dto/update-option.dto';
import { Option, OptionDocument } from './entity/option.entity';
import { PaginateQuery, Paginated, paginate } from 'nestjs-paginate';
interface PaginateModel<T> extends Model<T> {
paginate: (
query?: any,
options?: { page: number; limit: number; lean?: boolean; select?: string },
) => Promise<{
docs: T[];
totalDocs: number;
limit: number;
page: number;
totalPages: number;
hasNextPage: boolean;
hasPrevPage: boolean;
nextPage: number | null;
prevPage: number | null;
}>;
}
@Injectable()
export class OptionService {
constructor(
@InjectModel(Option.name)
private readonly optionModel: PaginateModel<OptionDocument>,
) {}
async create(data: CreateOptionDto): Promise<Option> {
const option = new this.optionModel({ ...data, id: require('uuid').v4() });
return option.save();
}
async findAll(page = 1, limit = 10): Promise<{
docs: Option[];
totalDocs: number;
limit: number;
page: number;
totalPages: number;
hasNextPage: boolean;
hasPrevPage: boolean;
nextPage: number | null;
prevPage: number | null;
}> {
return this.optionModel.paginate(
{},
{ page, limit, lean: true, select: 'id metadata status text createdAt updatedAt' },
);
}
async findById(id: string): Promise<Option | null> {
return this.optionModel.findOne({ id }).lean().exec();
}
async update(id: string, data: UpdateOptionDto): Promise<Option | null> {
return this.optionModel
.findOneAndUpdate({ id }, { ...data, updatedAt: new Date() }, { new: true })
.lean()
.exec();
}
async remove(id: string): Promise<void> {
await this.optionModel.deleteOne({ id }).exec();
}
}

View File

@ -2,8 +2,8 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose'; import { Document } from 'mongoose';
import { BaseEntity } from 'src/_core/_base.entity'; import { BaseEntity } from 'src/_core/_base.entity';
import * as mongoosePaginate from 'mongoose-paginate-v2'; import * as mongoosePaginate from 'mongoose-paginate-v2';
import { Attachment, AttachmentSchema } from './attachment.entity'; import { Attachment, AttachmentSchema } from '../../attachment/entity/attachment.entity';
import { Option, OptionSchema } from './option.entity'; import { Option, OptionSchema } from '../../option/entity/option.entity';
@Schema({ _id: false, id: false }) // Disable _id and virtual id @Schema({ _id: false, id: false }) // Disable _id and virtual id
export class Question extends BaseEntity { export class Question extends BaseEntity {
@ -16,11 +16,10 @@ export class Question extends BaseEntity {
@Prop({ @Prop({
type: String, type: String,
required: true, required: true,
// Example types: 'multiple-choice', 'text', 'rating', etc. enum: ['multiple-choice', 'single-choice', 'text'],
enum: ['multiple-choice', 'single-choice', 'text', 'rating', 'file'],
default: 'text', default: 'text',
}) })
type: string; type: 'multiple-choice' | 'single-choice' | 'text';
@Prop({ @Prop({
type: Boolean, type: Boolean,
@ -34,7 +33,7 @@ export class Question extends BaseEntity {
default: false, default: false,
required: true, required: true,
}) })
isConfidential: boolean; isConfidential: boolean; // no real-time updates for normal users
@Prop({ @Prop({
type: String, type: String,

View File

@ -30,11 +30,6 @@ export class QuestionService {
private readonly questionModel: PaginateModel<QuestionDocument>, private readonly questionModel: PaginateModel<QuestionDocument>,
) {} ) {}
/**
* Creates a new question.
* @param data - The data to create the question with.
* @returns The newly created question.
*/
async create(data: CreateQuestionDto): Promise<Question> { async create(data: CreateQuestionDto): Promise<Question> {
const question = new this.questionModel({ const question = new this.questionModel({
...data, ...data,
@ -44,12 +39,6 @@ export class QuestionService {
return question.save(); return question.save();
} }
/**
* Finds all questions with pagination.
* @param page - The current page number.
* @param limit - The number of items per page.
* @returns A paginated list of questions.
*/
async findAll( async findAll(
page = 1, page = 1,
limit = 10, limit = 10,
@ -73,11 +62,6 @@ export class QuestionService {
); );
} }
/**
* Finds a single question by its UUID.
* @param id - The UUID of the question.
* @returns The found question or null.
*/
async findById(id: string): Promise<Question | null> { async findById(id: string): Promise<Question | null> {
const selectFields = const selectFields =
'id text type isRequired isConfidential validationRules options attachments metadata status createdAt updatedAt'; 'id text type isRequired isConfidential validationRules options attachments metadata status createdAt updatedAt';
@ -92,12 +76,6 @@ export class QuestionService {
return question; return question;
} }
/**
* Updates a question by its UUID.
* @param id - The UUID of the question to update.
* @param data - The data to update the question with.
* @returns The updated question or null.
*/
async update( async update(
id: string, id: string,
data: UpdateQuestionDto, data: UpdateQuestionDto,
@ -120,10 +98,6 @@ export class QuestionService {
return updatedQuestion; return updatedQuestion;
} }
/**
* Removes a question by its UUID.
* @param id - The UUID of the question to remove.
*/
async remove(id: string): Promise<void> { async remove(id: string): Promise<void> {
const result = await this.questionModel.deleteOne({ id }).exec(); const result = await this.questionModel.deleteOne({ id }).exec();
if (result.deletedCount === 0) { if (result.deletedCount === 0) {