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:
@@ -1,2 +1,3 @@
|
||||
export { PROPERTY_REPOSITORY, type IPropertyRepository } from './property.repository';
|
||||
export { LISTING_REPOSITORY, type IListingRepository, type ListingSearchParams, type PaginatedResult } from './listing.repository';
|
||||
export type { ListingDetailData, ListingSearchItem, ListingSellerItem, ListingMediaData } from './listing-read.dto';
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { type ListingStatus, type TransactionType, type PropertyType, type Direction } from '@prisma/client';
|
||||
|
||||
/** Returned by findByIdWithProperty — full listing detail with property, seller, agent */
|
||||
export interface ListingDetailData {
|
||||
id: string;
|
||||
status: ListingStatus;
|
||||
transactionType: TransactionType;
|
||||
priceVND: string;
|
||||
pricePerM2: number | null;
|
||||
rentPriceMonthly: string | null;
|
||||
commissionPct: number | null;
|
||||
viewCount: number;
|
||||
saveCount: number;
|
||||
inquiryCount: number;
|
||||
publishedAt: string | null;
|
||||
createdAt: string;
|
||||
property: {
|
||||
id: string;
|
||||
propertyType: PropertyType;
|
||||
title: string;
|
||||
description: string;
|
||||
address: string;
|
||||
ward: string;
|
||||
district: string;
|
||||
city: string;
|
||||
areaM2: number;
|
||||
bedrooms: number | null;
|
||||
bathrooms: number | null;
|
||||
floors: number | null;
|
||||
direction: Direction | null;
|
||||
yearBuilt: number | null;
|
||||
legalStatus: string | null;
|
||||
amenities: unknown;
|
||||
projectName: string | null;
|
||||
media: ListingMediaData[];
|
||||
};
|
||||
seller: {
|
||||
id: string;
|
||||
fullName: string;
|
||||
phone: string;
|
||||
};
|
||||
agent: {
|
||||
id: string;
|
||||
userId: string;
|
||||
agency: string | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface ListingMediaData {
|
||||
id: string;
|
||||
url: string;
|
||||
type: string;
|
||||
order: number;
|
||||
caption: string | null;
|
||||
}
|
||||
|
||||
/** Returned by search / findByStatus — listing summary with thumbnail */
|
||||
export interface ListingSearchItem {
|
||||
id: string;
|
||||
status: ListingStatus;
|
||||
transactionType: TransactionType;
|
||||
priceVND: string;
|
||||
pricePerM2: number | null;
|
||||
viewCount: number;
|
||||
publishedAt: string | null;
|
||||
property: {
|
||||
id: string;
|
||||
propertyType: PropertyType;
|
||||
title: string;
|
||||
address: string;
|
||||
district: string;
|
||||
city: string;
|
||||
areaM2: number;
|
||||
bedrooms: number | null;
|
||||
bathrooms: number | null;
|
||||
thumbnail: string | null;
|
||||
};
|
||||
seller: {
|
||||
id: string;
|
||||
fullName: string;
|
||||
};
|
||||
}
|
||||
|
||||
/** Returned by findBySellerId — compact listing for seller dashboard */
|
||||
export interface ListingSellerItem {
|
||||
id: string;
|
||||
status: ListingStatus;
|
||||
transactionType: TransactionType;
|
||||
priceVND: string;
|
||||
property: {
|
||||
id: string;
|
||||
title: string;
|
||||
district: string;
|
||||
city: string;
|
||||
areaM2: number;
|
||||
thumbnail: string | null;
|
||||
};
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
import { type ListingStatus } from '@prisma/client';
|
||||
import { type ListingStatus, type TransactionType, type PropertyType } from '@prisma/client';
|
||||
import { type ListingEntity } from '../entities/listing.entity';
|
||||
import { type ListingDetailData, type ListingSearchItem, type ListingSellerItem } from './listing-read.dto';
|
||||
|
||||
export const LISTING_REPOSITORY = Symbol('LISTING_REPOSITORY');
|
||||
|
||||
export interface ListingSearchParams {
|
||||
status?: ListingStatus;
|
||||
transactionType?: string;
|
||||
propertyType?: string;
|
||||
transactionType?: TransactionType;
|
||||
propertyType?: PropertyType;
|
||||
city?: string;
|
||||
district?: string;
|
||||
minPrice?: bigint;
|
||||
@@ -28,10 +29,10 @@ export interface PaginatedResult<T> {
|
||||
|
||||
export interface IListingRepository {
|
||||
findById(id: string): Promise<ListingEntity | null>;
|
||||
findByIdWithProperty(id: string): Promise<{ listing: ListingEntity; property: any } | null>;
|
||||
findByIdWithProperty(id: string): Promise<ListingDetailData | null>;
|
||||
save(listing: ListingEntity): Promise<void>;
|
||||
update(listing: ListingEntity): Promise<void>;
|
||||
search(params: ListingSearchParams): Promise<PaginatedResult<any>>;
|
||||
findByStatus(status: ListingStatus, page: number, limit: number): Promise<PaginatedResult<any>>;
|
||||
findBySellerId(sellerId: string, page: number, limit: number): Promise<PaginatedResult<any>>;
|
||||
search(params: ListingSearchParams): Promise<PaginatedResult<ListingSearchItem>>;
|
||||
findByStatus(status: ListingStatus, page: number, limit: number): Promise<PaginatedResult<ListingSearchItem>>;
|
||||
findBySellerId(sellerId: string, page: number, limit: number): Promise<PaginatedResult<ListingSellerItem>>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user