added pagination, searching, sorting

This commit is contained in:
Suman991 2026-04-09 16:31:07 +05:30
parent 680918618b
commit 2079f3873c
5 changed files with 149 additions and 15 deletions

View File

@ -0,0 +1,47 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsEnum, IsInt, IsOptional, IsString, Max, Min } from 'class-validator';
export enum SortOrder {
ASC = 'asc',
DESC = 'desc',
}
export enum SortBy {
NAME = 'name',
CREATED_AT = 'createdAt',
UPDATED_AT = 'updatedAt',
STATUS = 'status',
}
export class QueryFormDto {
@ApiPropertyOptional({ example: 1 })
@Type(() => Number)
@IsInt()
@Min(1)
@IsOptional()
page?: number = 1;
@ApiPropertyOptional({ example: 10 })
@Type(() => Number)
@IsInt()
@Min(1)
@Max(100)
@IsOptional()
limit?: number = 10;
@ApiPropertyOptional({ example: '' })
@IsString()
@IsOptional()
search?: string;
@ApiPropertyOptional({ enum: SortBy, example: SortBy.CREATED_AT })
@IsEnum(SortBy)
@IsOptional()
sortBy?: SortBy = SortBy.CREATED_AT;
@ApiPropertyOptional({ enum: SortOrder, example: SortOrder.DESC })
@IsEnum(SortOrder)
@IsOptional()
sortOrder?: SortOrder = SortOrder.DESC;
}

View File

@ -6,12 +6,13 @@ import {
Patch,
Param,
Delete,
Query,
} from '@nestjs/common';
import { FormService } from './form.service';
import { ApiOperation } from '@nestjs/swagger';
import { UpdateFieldDto } from './dto/update-field.dto';
import { CreateUpdateDto } from './dto/create-update.dto';
import { QueryFormDto } from './dto/query-form.dto';
@Controller('form')
export class FormController {
@ -24,9 +25,9 @@ export class FormController {
}
@Get()
@ApiOperation({ summary: 'Find all forms' })
async findAll() {
return await this.formService.findAll();
@ApiOperation({ summary: 'Get all forms with pagination, search, sort' })
findAll(@Query() query: QueryFormDto) {
return this.formService.findAll(query);
}
@Get(':formId')

View File

@ -1,4 +1,3 @@
import { FormModule } from './form.module';
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Form, FormDocument, Status } from './schemas/form.schema';
@ -6,7 +5,20 @@ import { Model } from 'mongoose';
import { v4 as uuidv4 } from 'uuid';
import { UpdateFieldDto } from './dto/update-field.dto';
import { CreateUpdateDto } from './dto/create-update.dto';
import { QueryFormDto } from './dto/query-form.dto';
import { FormQueryBuilder } from './helpers/form-query.builder';
import { PaginatedResponse } from 'src/interfaces/paginated-response.interface';
// Reusable projections
const LIST_PROJECTION = {
_id: 0,
id: 1,
name: 1,
status: 1,
deletedAt: 1,
createdAt: 1,
};
const DETAIL_PROJECTION = { _id: 0, __v: 0 };
@Injectable()
export class FormService {
@ -30,18 +42,50 @@ export class FormService {
const updatedForm = await this.formModel.findOneAndUpdate(
{ id: dto.id },
{ $push: { fields: newField } },
{ new: true },
{ new: true, projection: DETAIL_PROJECTION },
);
if (!updatedForm) throw new NotFoundException(`Form ${dto.id} not found`);
return form;
return updatedForm;
}
async findAll(): Promise<Form[]> {
return await this.formModel.find().exec();
async findAll(query: QueryFormDto): Promise<PaginatedResponse<Form>> {
const filter = FormQueryBuilder.buildFilter(query);
const sort = FormQueryBuilder.buildSort(query);
const { skip, limit } = FormQueryBuilder.buildPagination(query);
// Run count and data fetch in parallel
const [total, data] = await Promise.all([
this.formModel.countDocuments(filter),
this.formModel
.find(filter)
.select(DETAIL_PROJECTION)
.sort(sort)
.skip(skip)
.limit(limit)
.exec(),
]);
const page = query.page ?? 1;
return {
data,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
hasNext: page * limit < total,
hasPrev: page > 1,
},
};
}
async find(formId: string): Promise<Form> {
const form = await this.formModel.findOne({ id: formId, deletedAt:null }).exec();
const form = await this.formModel
.findOne({ id: formId, deletedAt: null })
.select(LIST_PROJECTION)
.exec();
if (!form) throw new NotFoundException(`Form ${formId} not found`);
return form;
}
@ -63,6 +107,7 @@ export class FormService {
{
new: true,
arrayFilters: [{ 'elem.id': fieldId }],
projection: DETAIL_PROJECTION,
},
);
if (!form) throw new NotFoundException(`Form ${formId} not found`);
@ -73,7 +118,7 @@ export class FormService {
const form = await this.formModel.findOneAndUpdate(
{ id: formId },
{ $pull: { fields: { id: fieldId } } },
{ new: true },
{ new: true, projection: DETAIL_PROJECTION },
);
if (!form) throw new NotFoundException(`Form ${formId} not found`);
return form;
@ -87,7 +132,7 @@ export class FormService {
deletedAt: new Date(),
},
},
{ new: true },
{ new: true, projection: DETAIL_PROJECTION },
);
if (!form) throw new NotFoundException(`Form ${FormId} not found`);
@ -102,7 +147,7 @@ export class FormService {
deletedAt: null,
},
},
{ new: true },
{ new: true, projection: DETAIL_PROJECTION },
);
if (!form) throw new NotFoundException(`Form ${FormId} not found`);
return form;

View File

@ -0,0 +1,30 @@
import { QueryFormDto, SortOrder } from '../dto/query-form.dto';
export class FormQueryBuilder {
static buildFilter(query: QueryFormDto): Record<string, unknown> {
const filter: Record<string, unknown> = {};
if (query.search) {
filter.name = { $regex: query.search, $options: 'i' };
}
return filter;
}
// Build sort
static buildSort(query: QueryFormDto): Record<string, 1 | -1> {
return {
[query.sortBy ?? 'createdAt']: query.sortOrder === SortOrder.ASC ? 1 : -1,
};
}
// Build pagination
static buildPagination(query: QueryFormDto): { skip: number; limit: number } {
const page = query.page ?? 1;
const limit = query.limit ?? 10;
return {
skip: (page - 1) * limit,
limit,
};
}
}

View File

@ -0,0 +1,11 @@
export interface PaginatedResponse<T> {
data: T[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
hasNext: boolean;
hasPrev: boolean;
};
}