fix: eliminate untyped repository returns and standardize DomainException usage across all handlers

- Create typed DTOs (ListingDetailData, ListingSearchItem, ListingSellerItem) for repository read methods
- Replace all Promise<any> and PaginatedResult<any> with concrete types in repository interface and implementation
- Remove `as any` casts in search params by using Prisma enum types (TransactionType, PropertyType)
- Migrate all 16 handlers from NestJS built-in exceptions to domain exceptions (NotFoundException, ValidationException, etc.)
- Add CONTRIBUTING.md documenting error handling convention
- All 230 tests pass, typecheck clean

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-08 06:25:44 +07:00
parent 6389dcf78e
commit e9889539ea
28 changed files with 288 additions and 247 deletions

View File

@@ -39,8 +39,8 @@ import { UpdateListingStatusDto } from '../dto/update-listing-status.dto';
import { ModerateListingDto } from '../dto/moderate-listing.dto';
import { SearchListingsDto } from '../dto/search-listings.dto';
import { type CreateListingResult } from '../../application/commands/create-listing/create-listing.handler';
import { type ListingDetailDto } from '../../application/queries/get-listing/get-listing.handler';
import { type PaginatedResult } from '../../domain/repositories/listing.repository';
import { type ListingDetailData, type ListingSearchItem } from '../../domain/repositories/listing-read.dto';
@ApiTags('listings')
@Controller('listings')
@@ -109,7 +109,7 @@ export class ListingsController {
async getPendingModeration(
@Query('page') page?: number,
@Query('limit') limit?: number,
): Promise<PaginatedResult<any>> {
): Promise<PaginatedResult<ListingSearchItem>> {
return this.queryBus.execute(
new GetPendingModerationQuery(page ?? 1, limit ?? 20),
);
@@ -120,14 +120,14 @@ export class ListingsController {
@ApiResponse({ status: 200, description: 'Listing details returned' })
@ApiResponse({ status: 404, description: 'Listing not found' })
@Get(':id')
async getListing(@Param('id') id: string): Promise<ListingDetailDto> {
async getListing(@Param('id') id: string): Promise<ListingDetailData> {
return this.queryBus.execute(new GetListingQuery(id));
}
@ApiOperation({ summary: 'Search and filter property listings' })
@ApiResponse({ status: 200, description: 'Paginated search results' })
@Get()
async searchListings(@Query() dto: SearchListingsDto): Promise<PaginatedResult<any>> {
async searchListings(@Query() dto: SearchListingsDto): Promise<PaginatedResult<ListingSearchItem>> {
return this.queryBus.execute(
new SearchListingsQuery(
dto.status,