attachment done
This commit is contained in:
parent
bdeef357ba
commit
a74932f8bb
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { IsString, IsEnum, IsOptional, IsUUID, ValidateNested } from 'class-validator';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
import {MetadataDto} from './metadataEntry.dto';
|
||||||
|
|
||||||
|
export class BaseDto {
|
||||||
|
@IsUUID()
|
||||||
|
@IsOptional() // id is optional since BaseEntity generates it
|
||||||
|
id?: string;
|
||||||
|
|
||||||
|
@IsEnum(['active', 'inactive'])
|
||||||
|
@IsOptional()
|
||||||
|
status?: 'active'| 'inactive';
|
||||||
|
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => MetadataDto)
|
||||||
|
@IsOptional()
|
||||||
|
metadata?: MetadataDto; // Optional, defaults to { entries: [] }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
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[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { Controller, Get, Post, Patch, Delete, Param, Body, UsePipes, ValidationPipe, Query, ParseUUIDPipe, HttpCode, HttpStatus,} from '@nestjs/common';
|
||||||
|
import { AttachmentService } from './attachment.service';
|
||||||
|
import { CreateAttachmentDto } from './dto/create_attachment.dto';
|
||||||
|
import { UpdateAttachmentDto } from './dto/update_attachment.dto';
|
||||||
|
import { Attachment } from './entity/attachment.entity';
|
||||||
|
|
||||||
|
@Controller('attachments')
|
||||||
|
export class AttachmentController {
|
||||||
|
constructor(private readonly attachmentService: AttachmentService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async create(@Body() body: CreateAttachmentDto): Promise<Attachment> {
|
||||||
|
return this.attachmentService.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.attachmentService.findAll(parseInt(page, 10), parseInt(limit, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
async findOne(
|
||||||
|
@Param('id', new ParseUUIDPipe()) id: string,
|
||||||
|
): Promise<Attachment | null> {
|
||||||
|
return this.attachmentService.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||||
|
async update(
|
||||||
|
@Param('id', new ParseUUIDPipe()) id: string,
|
||||||
|
@Body() body: UpdateAttachmentDto,
|
||||||
|
): Promise<Attachment | null> {
|
||||||
|
return this.attachmentService.update(id, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
|
async remove(@Param('id', new ParseUUIDPipe()) id: string): Promise<void> {
|
||||||
|
return this.attachmentService.remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { MongooseModule } from '@nestjs/mongoose';
|
||||||
|
import { Attachment, AttachmentSchema } from './entity/attachment.entity';
|
||||||
|
import { AttachmentController } from './attachment.controller';
|
||||||
|
import { AttachmentService } from './attachment.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
MongooseModule.forFeature([
|
||||||
|
{ name: Attachment.name, schema: AttachmentSchema },
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
controllers: [AttachmentController],
|
||||||
|
providers: [AttachmentService],
|
||||||
|
})
|
||||||
|
export class AttachmentModule {}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
|
import { Model } from 'mongoose';
|
||||||
|
import { Attachment, AttachmentDocument } from './entity/attachment.entity';
|
||||||
|
import { CreateAttachmentDto } from './dto/create_attachment.dto';
|
||||||
|
import { UpdateAttachmentDto } from './dto/update_attachment.dto';
|
||||||
|
|
||||||
|
// Interface matching the mongoose-paginate-v2 plugin
|
||||||
|
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 AttachmentService {
|
||||||
|
constructor(
|
||||||
|
@InjectModel(Attachment.name)
|
||||||
|
private readonly attachmentModel: PaginateModel<AttachmentDocument>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async create(data: CreateAttachmentDto): Promise<Attachment> {
|
||||||
|
const attachment = new this.attachmentModel({
|
||||||
|
...data,
|
||||||
|
id: data.id || undefined, // Let BaseEntity generate UUID if not provided
|
||||||
|
metadata: data.metadata || { entries: [] },
|
||||||
|
});
|
||||||
|
return attachment.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll(
|
||||||
|
page = 1,
|
||||||
|
limit = 10,
|
||||||
|
): Promise<{
|
||||||
|
docs: Attachment[];
|
||||||
|
totalDocs: number;
|
||||||
|
limit: number;
|
||||||
|
page: number;
|
||||||
|
totalPages: number;
|
||||||
|
hasNextPage: boolean;
|
||||||
|
hasPrevPage: boolean;
|
||||||
|
nextPage: number | null;
|
||||||
|
prevPage: number | null;
|
||||||
|
}> {
|
||||||
|
// Selects all fields from the Attachment entity for the response
|
||||||
|
const selectFields =
|
||||||
|
'id text type isRequired isConfidential validationRules options attachments metadata status createdAt updatedAt';
|
||||||
|
return this.attachmentModel.paginate(
|
||||||
|
{},
|
||||||
|
{ page, limit, lean: true, select: selectFields },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findById(id: string): Promise<Attachment | null> {
|
||||||
|
const selectFields =
|
||||||
|
'id text type isRequired isConfidential validationRules options attachments metadata status createdAt updatedAt';
|
||||||
|
const attachment = await this.attachmentModel
|
||||||
|
.findOne({ id })
|
||||||
|
.select(selectFields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
if (!attachment) {
|
||||||
|
throw new NotFoundException(`Attachment with ID "${id}" not found`);
|
||||||
|
}
|
||||||
|
return attachment;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
id: string,
|
||||||
|
data: UpdateAttachmentDto,
|
||||||
|
): Promise<Attachment | null> {
|
||||||
|
const selectFields =
|
||||||
|
'id text type isRequired isConfidential validationRules options attachments metadata status createdAt updatedAt';
|
||||||
|
const updatedAttachment = await this.attachmentModel
|
||||||
|
.findOneAndUpdate(
|
||||||
|
{ id },
|
||||||
|
{ $set: { ...data, updatedAt: new Date() } },
|
||||||
|
{ new: true },
|
||||||
|
)
|
||||||
|
.select(selectFields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (!updatedAttachment) {
|
||||||
|
throw new NotFoundException(`Attachment with ID "${id}" not found`);
|
||||||
|
}
|
||||||
|
return updatedAttachment;
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: string): Promise<void> {
|
||||||
|
const result = await this.attachmentModel.deleteOne({ id }).exec();
|
||||||
|
if (result.deletedCount === 0) {
|
||||||
|
throw new NotFoundException(`Attachment with ID "${id}" not found`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// DTO for embedded Attachment
|
||||||
|
import { isEnum, IsEnum, IsOptional, IsString, IsUrl, IsUUID, ValidateNested } from 'class-validator';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
import { BaseDto } from '../../_core/dto/base.dto';
|
||||||
|
|
||||||
|
export class CreateAttachmentDto extends BaseDto{
|
||||||
|
@IsString()
|
||||||
|
displayName: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
filePath: string;
|
||||||
|
|
||||||
|
@IsEnum(['png','pdf','csv','jpg'])
|
||||||
|
fileType: 'png'|'pdf'|'csv'|'jpg';
|
||||||
|
|
||||||
|
@IsUrl()
|
||||||
|
storageUrl: string;
|
||||||
|
|
||||||
|
@IsUUID()
|
||||||
|
ownerId: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { PartialType } from '@nestjs/mapped-types';
|
||||||
|
import { CreateAttachmentDto } from './create_attachment.dto';
|
||||||
|
|
||||||
|
export class UpdateAttachmentDto extends PartialType(CreateAttachmentDto) {}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
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/entity/_base.entity';
|
||||||
|
|
||||||
@Schema({ _id: false, id: false }) // Embedded document, inheriting from BaseEntity
|
@Schema({ _id: false, id: false }) // Embedded document, inheriting from BaseEntity
|
||||||
export class Attachment extends BaseEntity {
|
export class Attachment extends BaseEntity {
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,8 @@
|
||||||
import { IsString, IsOptional, ValidateNested } from 'class-validator';
|
import { IsString, IsOptional, ValidateNested, IsUUID, IsEnum } from 'class-validator';
|
||||||
import { Type } from 'class-transformer';
|
import { BaseDto } from '../../_core/dto/base.dto';
|
||||||
|
|
||||||
class MetadataEntryDto {
|
// DTO for embedded Option
|
||||||
@IsString()
|
export class CreateOptionDto extends BaseDto{
|
||||||
key: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class MetadataDto {
|
|
||||||
@ValidateNested({ each: true })
|
|
||||||
@Type(() => MetadataEntryDto)
|
|
||||||
entries: MetadataEntryDto[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CreateOptionDto {
|
|
||||||
@IsString()
|
@IsString()
|
||||||
text: string;
|
text: string;
|
||||||
|
|
||||||
@ValidateNested()
|
|
||||||
@Type(() => MetadataDto)
|
|
||||||
@IsOptional()
|
|
||||||
metadata?: MetadataDto;
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
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/entity/_base.entity';
|
||||||
|
|
||||||
@Schema({ _id: false, id: false }) // Embedded document, inheriting from BaseEntity
|
@Schema({ _id: false, id: false }) // Embedded document, inheriting from BaseEntity
|
||||||
export class Option extends BaseEntity {
|
export class Option extends BaseEntity {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Controller, Get, Post, Patch, Delete, Param, Body, UsePipes, ValidationPipe } from '@nestjs/common';
|
import { Controller, Get, Post, Patch, Delete, Param, Body, UsePipes, ValidationPipe, Query } from '@nestjs/common';
|
||||||
import { ParseUUIDPipe } from '@nestjs/common';
|
import { ParseUUIDPipe } from '@nestjs/common';
|
||||||
import { OptionService } from './option.service';
|
import { OptionService } from './option.service';
|
||||||
import { CreateOptionDto } from './dto/create-option.dto';
|
import { CreateOptionDto } from './dto/create-option.dto';
|
||||||
|
|
@ -16,9 +16,13 @@ export class OptionController {
|
||||||
return this.optionService.create(body);
|
return this.optionService.create(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get('findAll')
|
||||||
async findAll(@Paginate() query: PaginateQuery): Promise<Paginated<Option>> {
|
async findAll(
|
||||||
return this.optionService.findAll(query);
|
@Query('page') page: string = '1',
|
||||||
|
@Query('limit') limit: string = '10',
|
||||||
|
) {
|
||||||
|
// The service returns the full pagination object
|
||||||
|
return this.optionService.findAll(parseInt(page, 10), parseInt(limit, 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,9 @@
|
||||||
import { IsString, IsEnum, IsOptional, IsUUID, ValidateNested } from 'class-validator';
|
import { IsString, IsEnum, IsOptional, IsUUID, ValidateNested } from 'class-validator';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
|
import { MetadataDto } from '../../_core/dto/metadataEntry.dto';
|
||||||
|
import { BaseDto } from '../../_core/dto/base.dto';
|
||||||
|
|
||||||
class MetadataEntryDto {
|
export class CreateParticipantDto extends BaseDto{
|
||||||
@IsString()
|
|
||||||
key: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class MetadataDto {
|
|
||||||
@ValidateNested({ each: true })
|
|
||||||
@Type(() => MetadataEntryDto)
|
|
||||||
entries: MetadataEntryDto[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CreateParticipantDto {
|
|
||||||
@IsUUID()
|
|
||||||
@IsOptional() // id is optional since BaseEntity generates it
|
|
||||||
id?: string;
|
|
||||||
|
|
||||||
@IsUUID()
|
@IsUUID()
|
||||||
userId: string; // Required, provided by client
|
userId: string; // Required, provided by client
|
||||||
|
|
||||||
|
|
@ -30,8 +14,4 @@ export class CreateParticipantDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
role?: 'moderator' | 'tester' | 'admin' | 'user'; // Optional, defaults to 'user'
|
role?: 'moderator' | 'tester' | 'admin' | 'user'; // Optional, defaults to 'user'
|
||||||
|
|
||||||
@ValidateNested()
|
|
||||||
@Type(() => MetadataDto)
|
|
||||||
@IsOptional()
|
|
||||||
metadata?: MetadataDto; // Optional, defaults to { entries: [] }
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { BaseEntity } from 'src/_core/_base.entity';
|
import { BaseEntity } from 'src/_core/entity/_base.entity';
|
||||||
import * as mongoosePaginate from 'mongoose-paginate-v2';
|
import * as mongoosePaginate from 'mongoose-paginate-v2';
|
||||||
import { Document } from 'mongoose';
|
import { Document } from 'mongoose';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,81 +1,11 @@
|
||||||
import { IsString, IsBoolean, IsOptional, IsUUID, ValidateNested, IsArray, IsEnum, IsUrl,} from 'class-validator';
|
import { IsString, IsBoolean, IsOptional, IsUUID, ValidateNested, IsArray, IsEnum, IsUrl,} from 'class-validator';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
|
import {CreateAttachmentDto} from '../../attachment/dto/create_attachment.dto';
|
||||||
// DTO for Metadata entries, consistent with participant DTO
|
import { BaseDto } from '../../_core/dto/base.dto';
|
||||||
class MetadataEntryDto {
|
import { CreateOptionDto } from '../../option/dto/create-option.dto';
|
||||||
@IsString()
|
|
||||||
key: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class MetadataDto {
|
|
||||||
@IsOptional()
|
|
||||||
@IsUUID()
|
|
||||||
id?: string;
|
|
||||||
|
|
||||||
@ValidateNested({ each: true })
|
|
||||||
@Type(() => MetadataEntryDto)
|
|
||||||
entries: MetadataEntryDto[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// DTO for embedded Option
|
|
||||||
class CreateOptionDto {
|
|
||||||
@IsOptional()
|
|
||||||
@IsUUID()
|
|
||||||
id?: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
text: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@ValidateNested()
|
|
||||||
@Type(() => MetadataDto)
|
|
||||||
metadata?: MetadataDto;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsEnum(['active', 'inactive'])
|
|
||||||
status?: 'active' | 'inactive';
|
|
||||||
}
|
|
||||||
|
|
||||||
// DTO for embedded Attachment
|
|
||||||
class CreateAttachmentDto {
|
|
||||||
@IsOptional()
|
|
||||||
@IsUUID()
|
|
||||||
id?: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
displayName: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
filePath: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
fileType: string;
|
|
||||||
|
|
||||||
@IsUrl()
|
|
||||||
storageUrl: string;
|
|
||||||
|
|
||||||
@IsUUID()
|
|
||||||
ownerId: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@ValidateNested()
|
|
||||||
@Type(() => MetadataDto)
|
|
||||||
metadata?: MetadataDto;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsEnum(['active', 'inactive'])
|
|
||||||
status?: 'active' | 'inactive';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main DTO for creating a Question
|
// Main DTO for creating a Question
|
||||||
export class CreateQuestionDto {
|
export class CreateQuestionDto extends BaseDto{
|
||||||
@IsOptional()
|
|
||||||
@IsUUID()
|
|
||||||
id?: string;
|
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
text: string;
|
text: string;
|
||||||
|
|
||||||
|
|
@ -94,11 +24,6 @@ export class CreateQuestionDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
validationRules?: string;
|
validationRules?: string;
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@ValidateNested()
|
|
||||||
@Type(() => MetadataDto)
|
|
||||||
metadata?: MetadataDto;
|
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
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/entity/_base.entity';
|
||||||
import * as mongoosePaginate from 'mongoose-paginate-v2';
|
import * as mongoosePaginate from 'mongoose-paginate-v2';
|
||||||
import { Attachment, AttachmentSchema } from '../../attachment/entity/attachment.entity';
|
import { Attachment, AttachmentSchema } from '../../attachment/entity/attachment.entity';
|
||||||
import { Option, OptionSchema } from '../../option/entity/option.entity';
|
import { Option, OptionSchema } from '../../option/entity/option.entity';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue