chore: update infrastructure configs, audit docs, and env template

- Update Docker Compose configs for Redis, Typesense, and MinIO services
- Update GitHub Actions deploy workflow with improved caching and steps
- Extend .env.example with Stringee, Zalo OA, and FCM config keys
- Update audit documentation with latest findings and recommendations
- Update CHANGELOG and README with recent feature additions

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-16 05:16:42 +07:00
parent 53c33a1c50
commit e78d706b42
26 changed files with 150 additions and 55 deletions

View File

@@ -28,6 +28,7 @@ export interface ListingDocument {
saveCount: number;
projectName: string | null;
amenities: string[];
isFeatured: number; // 1 if featuredUntil > now, 0 otherwise
}
export interface SearchResult {

View File

@@ -110,6 +110,7 @@ export class ListingIndexerService {
saveCount: l.saveCount,
projectName: p.projectName,
amenities: Array.isArray(p.amenities) ? (p.amenities as string[]) : [],
isFeatured: l.featuredUntil && l.featuredUntil > new Date() ? 1 : 0,
};
});
}
@@ -158,6 +159,7 @@ export class ListingIndexerService {
saveCount: listing.saveCount,
projectName: p.projectName,
amenities: Array.isArray(p.amenities) ? (p.amenities as string[]) : [],
isFeatured: listing.featuredUntil && listing.featuredUntil > new Date() ? 1 : 0,
};
}

View File

@@ -28,6 +28,7 @@ export interface RawListingRow {
saveCount: number;
projectName: string | null;
amenities: unknown;
featuredUntil?: Date | string | null;
}
/** Map a raw SQL row to the domain ListingDocument shape. */

View File

@@ -41,6 +41,7 @@ const LISTING_SCHEMA: CollectionCreateSchema = {
{ name: 'saveCount', type: 'int32', facet: false },
{ name: 'projectName', type: 'string', facet: true, optional: true },
{ name: 'amenities', type: 'string[]', facet: true, optional: true },
{ name: 'isFeatured', type: 'int32', facet: true },
],
token_separators: ['-', '_'],
enable_nested_fields: false,
@@ -159,16 +160,21 @@ export class TypesenseSearchRepository implements ISearchRepository {
}
}
// Featured listings always sort first, then by user-selected criteria
const featuredPrefix = 'isFeatured:desc';
switch (params.sortBy) {
case 'price_asc':
return 'priceVND:asc';
return `${featuredPrefix},priceVND:asc`;
case 'price_desc':
return 'priceVND:desc';
return `${featuredPrefix},priceVND:desc`;
case 'date_desc':
return 'publishedAt:desc';
return `${featuredPrefix},publishedAt:desc`;
case 'relevance':
default:
return params.query && params.query !== '*' ? '_text_match:desc,publishedAt:desc' : 'publishedAt:desc';
return params.query && params.query !== '*'
? `${featuredPrefix},_text_match:desc,publishedAt:desc`
: `${featuredPrefix},publishedAt:desc`;
}
}
}