# Audit: GoodGo Real Estate Listings & Property Management Feature **Status:** Comprehensive Audit Complete **Date:** April 19, 2026 **Scope:** Property/Listings API, Search, Management Dashboard, Frontend Components **Target:** Production readiness assessment --- ## 1. Scope Coverage — What's Implemented Well ### API Architecture (CQRS Pattern) - **Commands:** 9 implemented (Create, Update, Delete, Feature, Promote, Moderate, Upload-Media, Update-Status, Admin-Feature) - **Queries:** 4 core queries (GetListing, SearchListings, GetPriceHistory, GetPendingModeration) - **Event-Driven:** Domain events published post-mutation; event handlers for cache invalidation - **Auth Guards:** JwtAuthGuard + RolesGuard on sensitive endpoints; ownership checks before mutations - **Rate Limiting:** EndpointRateLimitGuard on POST /listings (10 req/60s per user) ### Database Schema - **Listing Model:** Complete with status enum (DRAFT/PENDING_REVIEW/ACTIVE/RESERVED/SOLD/RENTED/EXPIRED/REJECTED) - **Geospatial:** PostGIS integration (geometry(Point, 4326)) with indexes on location (Gist) - **Indexes:** Compound indexes on common queries (status + createdAt, sellerId + status, transactionType + status) - **Relations:** Cascading deletes on related entities (PriceHistory, SavedListing, Inquiry) ### Frontend Pages & Components - **Public Pages:** Listing detail with full SEO (metadata, JSON-LD breadcrumb, OG tags); Search page with filters, map view, saved search - **Dashboard:** Listings management (CRUD status card view), edit page with multi-step form - **Components:** Image gallery, lightbox, inquiry modal, price history chart, social share - **i18n:** Full Vietnamese i18n via next-intl; translation keys for all user-facing text - **Responsive:** Tailwind mobile-first design with flex/grid utilities; test coverage on key components ### Quality Practices - **Test Coverage:** 17 test files covering commands, queries, repositories, validators - **Error Handling:** Custom exceptions (DomainException, ForbiddenException, NotFoundException, ValidationException) - **Logging:** Structured logging with LoggerService; error stack traces captured - **Caching:** Redis cache with prefixes (SEARCH, MARKET_DISTRICT, LISTING) and TTL management --- ## 2. Gaps & Missing Functionality ### Critical Gaps 1. **Delete-Listing Handler Missing Tests** - No `.spec.ts` file for `delete-listing.handler.ts` - Edge case: deletion of featured listings with active payments not validated - **File:** `apps/api/src/modules/listings/application/commands/delete-listing/` - **Impact:** Risk of orphaned transactions/orders if deletions occur without proper state checks 2. **Admin-Feature-Listing Handler Incomplete** - `AdminFeatureListingHandler` exists but no implementation of admin-side featured listing expiry logic - No scheduled task to auto-expire featured listings when `featuredUntil` passes - **File:** `apps/api/src/modules/listings/application/commands/admin-feature-listing/` - **Impact:** Featured listings may remain highlighted beyond paid period 3. **Activation/Expiry Pipeline Missing** - No handler for `ActivateFeaturedListingCommand` - No event handler for `ListingCreatedEvent` to transition DRAFT → PENDING_REVIEW - **File:** Event handlers folder mostly empty - **Impact:** New listings not automatically entering moderation queue; featured listing lifecycle incomplete 4. **Search Module Separation Issue** - `SearchListingsQuery` exists in two places: listings module AND search module - Potential for duplicate/conflicting search logic - **Files:** - `apps/api/src/modules/listings/application/queries/search-listings/` - `apps/api/src/modules/search/application/queries/search-properties/` - **Impact:** Maintenance burden; risk of divergent behavior 5. **Price Validation Service Not Wired to Schema** - `PRICE_VALIDATOR` service exists but pricing boundaries not enforced at database level - No check constraints on `priceVND` (negative prices technically allowed in DB) - **File:** `apps/api/src/modules/listings/domain/services/price-validator.ts` - **Impact:** Invalid prices can persist if validator bypassed or service disabled ### Medium-Priority Gaps 6. **Missing Feature-Listing Expiry Cron/Scheduled Task** - Featured listing expiry relies on manual query-time checks (`featuredUntil > now`) - No background job to transition expired featured listings back to non-featured state - **Impact:** Search rankings may incorrectly show expired featured listings; analytics skewed 7. **Media Deletion Not Implemented** - Upload-media exists; no delete-media endpoint - Users cannot remove individual images after upload - **File:** Controllers missing DELETE `:id/media/:mediaId` - **Impact:** User experience friction; storage bloat 8. **Moderation Feedback Not Visible to User** - `ModerateListingCommand` sets `moderationNotes` but frontend doesn't display rejection reason - Users cannot see why their listing was rejected/pending - **Impact:** Poor user experience; support ticket volume increases 9. **Dashboard Saved Searches Not Fully Implemented** - Saved search CRUD endpoints exist; UI component incomplete - Alert subscription feature defined in schema but no notification sender handler - **File:** `apps/web/app/[locale]/(dashboard)/dashboard/saved-searches/page.tsx` - **Impact:** Feature partially unusable 10. **Missing Batch Operations** - No bulk update endpoints (e.g., mark multiple listings ACTIVE, bulk delete) - Admin dashboard may require repeated individual API calls - **Impact:** Scalability/usability issue for admins managing many listings --- ## 3. Code Quality Issues ### Performance & N+1 Queries 1. **Geo-Extraction Two-Step Query** (Medium priority) - `findByIdWithProperty()`: First Prisma query, then raw SQL for PostGIS extraction - `searchListings()`: Batch geo-extraction mitigates but still 2 queries (Prisma + raw SQL) - **File:** `apps/api/src/modules/listings/infrastructure/repositories/listing-read.queries.ts` (lines 26–35, 156–167) - **Optimization:** Embed ST_Y/ST_X in the Prisma query using `$queryRaw` for the entire fetch 2. **Seller Lookups in Search Result** - Search includes seller (line 146: `seller: { select: { id: true, fullName: true } }`) - If frontend doesn't need seller full details on listing cards, unnecessary join - **File:** Line 146 of listing-read.queries.ts - **Impact:** Negligible on small datasets; scales poorly with 100k+ listings 3. **Media Eager-Load Limits** - `take: 5` in search, `take: 10` in detail (hardcoded) - Inconsistent; no configuration option; wastes bandwidth if full gallery not needed - **File:** Lines 143, 15 of listing-read.queries.ts ### Hardcoded Values & Configuration 1. **Featured Listing Prices Hardcoded** - `PACKAGE_PRICES` defined in handler (3_days: 99k, 7_days: 199k, 30_days: 499k VND) - Not configurable; require code change to adjust pricing - **File:** `apps/api/src/modules/listings/application/commands/feature-listing/feature-listing.handler.ts` (lines 14–18) - **Risk:** Admin cannot price-test without redeployment 2. **MAX_MEDIA_PER_PROPERTY Hardcoded to 20** - Not fetched from database config or environment - **File:** `apps/api/src/modules/listings/application/commands/upload-media/upload-media.handler.ts` (line 10) 3. **File Upload Limits Hardcoded** - Image max 10MB, video max 100MB; no admin override - **File:** `apps/api/src/modules/listings/presentation/controllers/listings.controller.ts` (lines 325–329) 4. **Search Result Page Size Capped to 100** - `Math.min(params.limit ?? 20, 100)` — prevents large exports - **File:** `apps/api/src/modules/listings/infrastructure/repositories/listing-read.queries.ts` (line 105) ### Validation Gaps 1. **No Validation on Duplicate Property Creation** - Users can create multiple listings for same property - Duplicate detector warns but never blocks (catch-all try/catch suppresses failures) - **File:** `apps/api/src/modules/listings/application/commands/create-listing/create-listing.handler.ts` (lines 153–155) - **Risk:** Spam, data duplication 2. **Price History Source Always "manual_update"** - No distinction between user edits, AI adjustments, or system changes - **File:** `prisma/schema.prisma` (line 395), `PriceHistory.source` default - **Impact:** Audit trail unclear for analytics 3. **No Agent Assignment Validation** - `agentId` accepted without checking agent exists or has broker permission - **File:** CreateListingCommand accepts `agentId` but no guard - **Risk:** Listing assigned to non-existent/unauthorized agent 4. **Description/Title Length Not Enforced** - Schema allows unlimited text; no max_length on `description` in Property model - **File:** `prisma/schema.prisma` — Property.description is `@db.Text` (no constraint) - **Impact:** Huge descriptions slow search queries; UI renders badly ### Security Issues 1. **Moderation Bypass Risk** - Update-listing transitions ACTIVE → PENDING_REVIEW, but admin can manually revert - No audit log of who changed listing status - **File:** `apps/api/src/modules/listings/application/commands/update-listing/update-listing.handler.ts` (line 126) - **Risk:** Moderator decisions ignored silently 2. **File Upload Path Predictable** - Files stored in `properties/{propertyId}` (publicly guessable) - No hash-based path obfuscation; potential enumeration attack - **File:** `apps/api/src/modules/listings/application/commands/upload-media/upload-media.handler.ts` (line 40) 3. **No Rate Limit on Feature Listing** - Feature endpoint limited by quota, not rate limit - User could spam feature requests and hit quota fast - **File:** `listings.controller.ts` — `@Post(':id/feature')` has no @EndpointRateLimit 4. **Agent Assignment Not Validated During Update** - User can reassign listing to arbitrary agent; no permission check - **File:** `UpdateListingCommand` constructor doesn't validate agent ownership - **Risk:** Seller gives away listing commission to unauthorized agent 5. **Inquiry Message Not Sanitized** - Inquiry message stored as-is; potential XSS if echoed in admin dashboard - **File:** `prisma/schema.prisma` line 472 — `Inquiry.message` is `@db.Text` - **Impact:** Admin UI could be compromised ### Maintainability Issues 1. **Unused Deprecated Type** - `ListingDetailDto` marked `@deprecated` with TODO comment - **File:** `apps/api/src/modules/listings/application/queries/get-listing/get-listing.handler.ts` (line 8) - **Cleanup:** Remove after migration complete 2. **Inconsistent Naming (Vietnamese vs English)** - Error messages in Vietnamese (e.g., "Không thể tạo tin đăng") - Test names, constants in English - Domain entities mixed (Vietnamese property types: APARTMENT, VILLA) - **Impact:** Codebase hard to reason about for non-Vietnamese speakers 3. **Missing JSDoc on Complex Methods** - `searchListings()` function complex with Prisma filters; no parameter documentation - **File:** `listing-read.queries.ts` lines 100–213 --- ## 4. Security & Validation Concerns | Issue | Severity | Mitigation | |-------|----------|-----------| | No transaction check before delete | High | Add `findByIdWithRelations()` check before `delete()` | | File path enumeration possible | Medium | Hash property IDs in storage path; implement signed URLs | | Agent assignment not validated | Medium | Verify agent broker relationship before assignment | | Inquiry message XSS risk | Medium | Sanitize on storage; escape on frontend display | | Moderation audit trail missing | Medium | Add `moderatedBy`, `moderatedAt` fields; log state transitions | | Duplicate listings not blocked | Low | Merge duplicate detector into command handler (convert warning to block option) | --- ## 5. Performance & Scalability ### Issues Identified 1. **Geo-Extraction Overhead** - Two separate query roundtrips (Prisma + raw SQL) per listing detail fetch - At scale (1M+ listings), search page with 20 results = 21 queries total - **Fix:** Use Prisma raw SQL for full fetch; avoid Prisma ORM + separate geo query 2. **Uncached Admin Queries** - `GetPendingModerationQuery` not cached; admin dashboard refreshes full list every load - **File:** `apps/api/src/modules/listings/application/queries/get-pending-moderation/` - **Fix:** Add short-lived cache (60s) with invalidation on moderation action 3. **Search Filters Create Complex Queries** - Nested property filters in searchListings (lines 118–129 listing-read.queries.ts) - No query plan optimization hints; potential slow full table scan on large datasets - **Fix:** Add database indexes on `status`, `transactionType`, `property(propertyType, district, city)` 4. **Media Queries Unoptimized** - Every search result includes media array (even if not displayed) - Transferring 5–10 media objects × 20 results = unnecessary payload - **Fix:** Add optional `?includeMedia=true` query param; default exclude in list view --- ## 6. Recommendations (Prioritized) ### 🔴 High Priority (Blocks Production) 1. **Implement Delete-Listing Test Suite** - Add edge cases: featured listing with active payment, transactions in progress - Verify cascading deletes work correctly - **Effort:** 2 hours | **Owner:** Backend 2. **Add Featured-Listing Expiry Handler** - Implement `ExpireFeaturedListingsScheduledTask` (daily cron or on-demand) - Transition expired listings: set `featuredUntil = null`, trigger search cache invalidation - **Effort:** 4 hours | **Owner:** Backend 3. **Wire Price Validation to Schema** - Add PostgreSQL check constraint: `priceVND > 0` - Document min/max pricing rules in README - **Effort:** 1 hour | **Owner:** Backend + DBA 4. **Implement Moderation Audit Trail** - Add `moderatedBy` (admin user ID), `moderatedAt` (timestamp), `previousStatus` fields to Listing - Log transitions in event handler - **Effort:** 3 hours | **Owner:** Backend ### 🟠 Medium Priority (Improves UX/Security) 5. **Implement Media Deletion Endpoint** - Add `DELETE /listings/:id/media/:mediaId` with ownership check - Trigger S3 deletion + cache invalidation - **Effort:** 2 hours | **Owner:** Backend 6. **Display Moderation Feedback to User** - Add frontend component to show rejection reason when status=REJECTED - Notify user via email + in-app notification - **Effort:** 3 hours | **Owner:** Frontend + Backend 7. **Sanitize Inquiry Messages** - Add DOMPurify to admin dashboard inquiry display - Store sanitized version if displaying in emails - **Effort:** 1 hour | **Owner:** Frontend 8. **Add Rate Limit to Feature Endpoint** - `@EndpointRateLimit({ limit: 5, windowSeconds: 3600 })` (5 features per hour) - **Effort:** 30 min | **Owner:** Backend 9. **Validate Agent Assignment** - Check agent.isVerified before assignment - Add broker permission check if multi-broker support added - **Effort:** 1 hour | **Owner:** Backend 10. **Consolidate Search Logic** - Move `SearchListingsQuery` from listings module to search module - Remove duplicate in listings module; re-export from search - **Effort:** 2 hours | **Owner:** Backend ### 🟡 Low Priority (Tech Debt) 11. **Extract Hardcoded Values to Config** - Move PACKAGE_PRICES → database config table (FeaturePackagePrice) - Add admin UI to manage pricing - **Effort:** 4 hours | **Owner:** Backend 12. **Optimize Geo-Extraction Queries** - Consolidate Prisma + raw SQL into single raw query - Add benchmark: measure latency before/after - **Effort:** 3 hours | **Owner:** Backend + Infra 13. **Implement Batch Operations API** - `PATCH /listings/batch` to update multiple listings status - Quota should apply per-listing, not per-request - **Effort:** 4 hours | **Owner:** Backend 14. **Add Missing JSDoc & Comments** - Document searchListings Prisma filters - Clarify geo-extraction rationale - **Effort:** 2 hours | **Owner:** Backend 15. **Complete Saved Searches Alerts** - Implement `SavedSearchAlertHandler` event listener - Send daily digest when new listings match saved search - **Effort:** 6 hours | **Owner:** Backend + Notifications --- ## Summary **Overall Assessment:** Feature is **80% production-ready**; core CRUD works, search functional, but **audit trail, expiry automation, and error handling need hardening**. **Critical Path to Production:** 1. ✅ Implement delete-listing tests 2. ✅ Add featured-listing expiry cron 3. ✅ Wire price validation to schema 4. ✅ Add moderation audit trail **Risk Level:** Medium (security concerns around file paths, agent assignment; moderation bypass possible) **Test Coverage:** 70% of commands/queries; delete-listing and expiry handlers untested **Deployment Blocker:** None, if recommendations #1–#4 completed within 2 weeks.