diff --git a/apps/api/src/modules/listings/domain/repositories/listing-read.dto.ts b/apps/api/src/modules/listings/domain/repositories/listing-read.dto.ts index 0c0977c..74178a4 100644 --- a/apps/api/src/modules/listings/domain/repositories/listing-read.dto.ts +++ b/apps/api/src/modules/listings/domain/repositories/listing-read.dto.ts @@ -12,6 +12,8 @@ export interface ListingDetailData { viewCount: number; saveCount: number; inquiryCount: number; + isFeatured: boolean; + featuredUntil: string | null; publishedAt: string | null; createdAt: string; property: { @@ -63,6 +65,8 @@ export interface ListingSearchItem { transactionType: TransactionType; priceVND: string; pricePerM2: number | null; + isFeatured: boolean; + featuredUntil: string | null; viewCount: number; publishedAt: string | null; property: { @@ -92,6 +96,8 @@ export interface ListingSellerItem { status: ListingStatus; transactionType: TransactionType; priceVND: string; + isFeatured: boolean; + featuredUntil: string | null; property: { id: string; title: string; diff --git a/apps/api/src/modules/listings/infrastructure/repositories/listing-read.queries.ts b/apps/api/src/modules/listings/infrastructure/repositories/listing-read.queries.ts index e84bdfb..53f3880 100644 --- a/apps/api/src/modules/listings/infrastructure/repositories/listing-read.queries.ts +++ b/apps/api/src/modules/listings/infrastructure/repositories/listing-read.queries.ts @@ -34,6 +34,7 @@ export async function findByIdWithProperty( // location is NOT NULL in the database — geo extraction always succeeds for existing properties const geo = geoRows[0]!; + const now = new Date(); return { id: listing.id, status: listing.status, @@ -45,6 +46,8 @@ export async function findByIdWithProperty( viewCount: listing.viewCount, saveCount: listing.saveCount, inquiryCount: listing.inquiryCount, + isFeatured: listing.featuredUntil != null && listing.featuredUntil > now, + featuredUntil: listing.featuredUntil?.toISOString() ?? null, publishedAt: listing.publishedAt?.toISOString() ?? null, createdAt: listing.createdAt.toISOString(), property: { @@ -116,7 +119,10 @@ export async function searchListings( where, skip, take: limit, - orderBy: { createdAt: 'desc' }, + orderBy: [ + { featuredUntil: { sort: 'desc', nulls: 'last' } }, + { createdAt: 'desc' }, + ], include: { property: { include: { @@ -146,6 +152,7 @@ export async function searchListings( } } + const now = new Date(); return { data: data.map((listing) => { // location is NOT NULL — every property in the result set has geo data @@ -156,6 +163,8 @@ export async function searchListings( transactionType: listing.transactionType, priceVND: listing.priceVND.toString(), pricePerM2: listing.pricePerM2, + isFeatured: listing.featuredUntil != null && listing.featuredUntil > now, + featuredUntil: listing.featuredUntil?.toISOString() ?? null, viewCount: listing.viewCount, publishedAt: listing.publishedAt?.toISOString() ?? null, property: { @@ -213,12 +222,15 @@ export async function findBySellerIdQuery( prisma.listing.count({ where }), ]); + const sellerNow = new Date(); return { data: data.map((listing) => ({ id: listing.id, status: listing.status, transactionType: listing.transactionType, priceVND: listing.priceVND.toString(), + isFeatured: listing.featuredUntil != null && listing.featuredUntil > sellerNow, + featuredUntil: listing.featuredUntil?.toISOString() ?? null, property: { id: listing.property.id, title: listing.property.title,