fix: add take limits on media includes and enforce pagination validation
- Add take: 10 on unbounded media include in findByIdWithProperty - Add take: 100 + orderBy on user listings include in getUserDetail - Convert GetUsersQueryDto page/limit from string to validated integers with @Min(1) @Max(100) - Add @Max(100) to BillingHistoryParamsDto limit field - Refactor admin controller to use GetUsersQueryDto with class-validator pipeline Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -212,6 +212,8 @@ export class PrismaAdminQueryRepository implements IAdminQueryRepository {
|
||||
},
|
||||
listings: {
|
||||
select: { id: true, status: true },
|
||||
take: 100,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -40,6 +40,7 @@ import { UpdateUserStatusDto } from '../dto/update-user-status.dto';
|
||||
import { ApproveKycDto } from '../dto/approve-kyc.dto';
|
||||
import { RejectKycDto } from '../dto/reject-kyc.dto';
|
||||
import { BulkModerateDto } from '../dto/bulk-moderate.dto';
|
||||
import { GetUsersQueryDto } from '../dto/get-users-query.dto';
|
||||
|
||||
import { type ApproveListingResult } from '../../application/commands/approve-listing/approve-listing.handler';
|
||||
import { type RejectListingResult } from '../../application/commands/reject-listing/reject-listing.handler';
|
||||
@@ -136,28 +137,19 @@ export class AdminController {
|
||||
|
||||
@Get('users')
|
||||
@ApiOperation({ summary: 'List users with optional filters' })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number, description: 'Page number (default 1)' })
|
||||
@ApiQuery({ name: 'limit', required: false, type: Number, description: 'Items per page (default 20)' })
|
||||
@ApiQuery({ name: 'role', required: false, type: String, description: 'Filter by role (BUYER, SELLER, AGENT, ADMIN)' })
|
||||
@ApiQuery({ name: 'isActive', required: false, type: String, description: 'Filter by active status (true/false)' })
|
||||
@ApiQuery({ name: 'search', required: false, type: String, description: 'Search by name or email' })
|
||||
@ApiResponse({ status: 200, description: 'User list retrieved successfully' })
|
||||
@ApiResponse({ status: 401, description: 'Unauthorized – missing or invalid JWT' })
|
||||
@ApiResponse({ status: 403, description: 'Forbidden – requires ADMIN role' })
|
||||
async getUsers(
|
||||
@Query('page') page?: string,
|
||||
@Query('limit') limit?: string,
|
||||
@Query('role') role?: string,
|
||||
@Query('isActive') isActive?: string,
|
||||
@Query('search') search?: string,
|
||||
@Query() query: GetUsersQueryDto,
|
||||
): Promise<UserListResult> {
|
||||
return this.queryBus.execute(
|
||||
new GetUsersQuery(
|
||||
page ? parseInt(page, 10) : 1,
|
||||
limit ? parseInt(limit, 10) : 20,
|
||||
role,
|
||||
isActive !== undefined ? isActive === 'true' : undefined,
|
||||
search,
|
||||
query.page ?? 1,
|
||||
query.limit ?? 20,
|
||||
query.role,
|
||||
query.isActive,
|
||||
query.search,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { IsOptional, IsString, IsIn } from 'class-validator';
|
||||
import { Transform } from 'class-transformer';
|
||||
import { IsOptional, IsString, IsIn, IsBoolean, IsInt, Min, Max } from 'class-validator';
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
export class GetUsersQueryDto {
|
||||
@ApiPropertyOptional({ description: 'Page number', example: '1' })
|
||||
@ApiPropertyOptional({ description: 'Page number', example: 1, minimum: 1 })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
page?: string;
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
page?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Items per page', example: '20' })
|
||||
@ApiPropertyOptional({ description: 'Items per page', example: 20, minimum: 1, maximum: 100 })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
limit?: string;
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(100)
|
||||
limit?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Filter by user role', enum: ['BUYER', 'SELLER', 'AGENT', 'ADMIN'] })
|
||||
@IsOptional()
|
||||
@@ -20,9 +25,9 @@ export class GetUsersQueryDto {
|
||||
|
||||
@ApiPropertyOptional({ description: 'Filter by active status (true/false)', example: 'true' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Transform(({ value }) => value === 'true' ? true : value === 'false' ? false : undefined)
|
||||
isActive?: string;
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Search by name or email', example: 'john' })
|
||||
@IsOptional()
|
||||
|
||||
@@ -20,7 +20,7 @@ export class PrismaListingRepository implements IListingRepository {
|
||||
include: {
|
||||
property: {
|
||||
include: {
|
||||
media: { orderBy: { order: 'asc' } },
|
||||
media: { orderBy: { order: 'asc' }, take: 10 },
|
||||
},
|
||||
},
|
||||
seller: { select: { id: true, fullName: true, phone: true } },
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { IsInt, IsOptional, Min } from 'class-validator';
|
||||
import { IsInt, IsOptional, Max, Min } from 'class-validator';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
|
||||
export class BillingHistoryParamsDto {
|
||||
@ApiPropertyOptional({ description: 'Maximum number of records to return', minimum: 1, example: 20 })
|
||||
@ApiPropertyOptional({ description: 'Maximum number of records to return', minimum: 1, maximum: 100, example: 20 })
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => parseInt(value, 10))
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(100)
|
||||
limit?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Number of records to skip', minimum: 0, example: 0 })
|
||||
|
||||
Reference in New Issue
Block a user