feat(listings): add user-facing scam/abuse report flow (GOO-19)

- Add ListingFlag model with FlagReason enum (SCAM, DUPLICATE, WRONG_INFO, ALREADY_SOLD, INAPPROPRIATE)
- Add POST /listings/:id/report endpoint with rate limiting and duplicate prevention
- Auto-flag listings with ≥3 reports to PENDING_REVIEW for moderator review
- Add GET /admin/flagged-listings endpoint for admin moderation queue
- Add "Báo cáo" button + modal on listing detail page (Vietnamese UI)
- Add Prisma migration for listing_flags table with unique constraint per user/listing

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-23 00:19:12 +07:00
parent 94d462ef4f
commit 0329455e9a
26 changed files with 615 additions and 57 deletions

View File

@@ -25,6 +25,16 @@ export type Direction =
export type Furnishing = 'FULLY_FURNISHED' | 'BASIC_FURNISHED' | 'UNFURNISHED';
export type PropertyCondition = 'NEW' | 'LIKE_NEW' | 'RENOVATED' | 'USED';
export type LegalStatus = 'SO_DO' | 'SO_HONG' | 'LAND_USE_RIGHT' | 'JOINT_USE_RIGHT' | 'AWAITING' | 'NO_CERTIFICATE';
export type FlagReason = 'SCAM' | 'DUPLICATE' | 'WRONG_INFO' | 'ALREADY_SOLD' | 'INAPPROPRIATE';
export interface ReportListingResult {
flagId: string;
listingId: string;
totalReports: number;
autoFlagged: boolean;
}
// ─── Interfaces ──────────────────────────────────────────
@@ -99,7 +109,8 @@ export interface ListingDetail {
totalFloors: number | null;
direction: Direction | null;
yearBuilt: number | null;
legalStatus: string | null;
legalStatus: LegalStatus | null;
certificateVerified: boolean;
amenities: string[] | null;
nearbyPOIs: unknown;
metroDistanceM: number | null;
@@ -303,4 +314,7 @@ export const listingsApi = {
`/analytics/neighborhoods/${encodeURIComponent(district)}/score?city=${encodeURIComponent(city)}`,
)
.then((res) => res.data),
reportListing: (listingId: string, reason: FlagReason, description?: string) =>
apiClient.post<ReportListingResult>(`/listings/${listingId}/report`, { reason, description }),
};