feat(search): implement Search module with Typesense full-text & geo search
- TypesenseClient service with configurable connection - Collection schema for listings with facets, geo-point, and Vietnamese text - ListingIndexer service with PostGIS coordinate extraction for geo search - CQRS commands: SyncListing, ReindexAll (batch with pagination) - CQRS queries: SearchProperties (filters, sorting), GeoSearch (radius) - Event handlers for listing.approved/updated/deactivated auto-sync - REST endpoints: GET /search, GET /search/geo, POST /search/reindex (admin) - DTOs with class-validator validation and pagination Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsNumber,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
Min,
|
||||
Max,
|
||||
} from 'class-validator';
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
|
||||
export enum SortByOption {
|
||||
PRICE_ASC = 'price_asc',
|
||||
PRICE_DESC = 'price_desc',
|
||||
DATE_DESC = 'date_desc',
|
||||
RELEVANCE = 'relevance',
|
||||
}
|
||||
|
||||
export class SearchPropertiesDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
q?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
propertyType?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
transactionType?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
priceMin?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
priceMax?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
areaMin?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
areaMax?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
bedrooms?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
district?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
city?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(SortByOption)
|
||||
sortBy?: SortByOption;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Transform(({ value }) => value ?? 1)
|
||||
page?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(100)
|
||||
@Transform(({ value }) => value ?? 20)
|
||||
perPage?: number;
|
||||
}
|
||||
Reference in New Issue
Block a user