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:
@@ -14,6 +14,7 @@ export { RedisService } from './redis.service';
|
||||
export { RedisIoAdapter } from './redis-io.adapter';
|
||||
export { CacheService, CachePrefix, CacheTTL } from './cache.service';
|
||||
export { LoggerService } from './logger.service';
|
||||
export { TypesenseClientService } from './typesense-client.service';
|
||||
export { EventBusService } from './event-bus.service';
|
||||
export { GlobalExceptionFilter } from './filters/global-exception.filter';
|
||||
export { CorrelationIdMiddleware } from './middleware/correlation-id.middleware';
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Injectable, type OnModuleInit } from '@nestjs/common';
|
||||
import { Client as TypesenseClient } from 'typesense';
|
||||
import { LoggerService } from './logger.service';
|
||||
|
||||
/**
|
||||
* Provides a shared Typesense client for search, indexers, and health probes.
|
||||
* Lives in SharedModule so any feature module can inject it without importing
|
||||
* SearchModule.
|
||||
*/
|
||||
@Injectable()
|
||||
export class TypesenseClientService implements OnModuleInit {
|
||||
private client!: TypesenseClient;
|
||||
|
||||
constructor(private readonly logger: LoggerService) {}
|
||||
|
||||
onModuleInit(): void {
|
||||
this.client = new TypesenseClient({
|
||||
nodes: [
|
||||
{
|
||||
host: process.env['TYPESENSE_HOST'] || 'localhost',
|
||||
port: parseInt(process.env['TYPESENSE_PORT'] || '8108', 10),
|
||||
protocol: process.env['TYPESENSE_PROTOCOL'] || 'http',
|
||||
},
|
||||
],
|
||||
apiKey: process.env['TYPESENSE_API_KEY'] || 'ts_dev_key_change_me',
|
||||
connectionTimeoutSeconds: 5,
|
||||
retryIntervalSeconds: 0.1,
|
||||
numRetries: 3,
|
||||
});
|
||||
|
||||
this.logger.log('TypesenseClientService initialized', 'TypesenseClient');
|
||||
}
|
||||
|
||||
getClient(): TypesenseClient {
|
||||
return this.client;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { RequestLoggingMiddleware } from './infrastructure/middleware/request-lo
|
||||
import { SanitizeInputMiddleware } from './infrastructure/middleware/sanitize-input.middleware';
|
||||
import { PrismaService } from './infrastructure/prisma.service';
|
||||
import { RedisService } from './infrastructure/redis.service';
|
||||
import { TypesenseClientService } from './infrastructure/typesense-client.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
@@ -34,6 +35,7 @@ import { RedisService } from './infrastructure/redis.service';
|
||||
RedisService,
|
||||
CacheService,
|
||||
EventBusService,
|
||||
TypesenseClientService,
|
||||
makeCounterProvider({
|
||||
name: CACHE_HIT_TOTAL,
|
||||
help: 'Total number of cache hits',
|
||||
@@ -54,7 +56,7 @@ import { RedisService } from './infrastructure/redis.service';
|
||||
useClass: GlobalExceptionFilter,
|
||||
},
|
||||
],
|
||||
exports: [PrismaService, RedisService, CacheService, LoggerService, EventBusService, FieldEncryptionService, PrometheusModule],
|
||||
exports: [PrismaService, RedisService, CacheService, LoggerService, EventBusService, FieldEncryptionService, TypesenseClientService, PrometheusModule],
|
||||
})
|
||||
export class SharedModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer): void {
|
||||
|
||||
Reference in New Issue
Block a user