docs: consolidate exploration & audit reports under docs/ (TEC-3094)
- Move 8 stray .md (+5 .txt) from ~/Desktop into docs/explorations/from-desktop/ - Reorganize 27 .md/.txt at workspace root: - audit reports -> docs/audits/ - exploration reports -> docs/explorations/ - design system -> docs/design-system/ - Keep only README/CHANGELOG/CONTRIBUTING/CLAUDE at repo root - Refresh docs/README.md as canonical index with links to all groups - Note: pre-existing docs/audits/AUDIT_INDEX.md and AUDIT_SUMMARY.md were overwritten by the newer root-level versions during the move Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
312
docs/explorations/from-desktop/00_SUMMARY.md
Normal file
312
docs/explorations/from-desktop/00_SUMMARY.md
Normal file
@@ -0,0 +1,312 @@
|
||||
# 🎯 Analytics Module Architecture Exploration — Complete
|
||||
|
||||
## Documents Created
|
||||
|
||||
I've prepared **3 comprehensive guides** for you:
|
||||
|
||||
### 1️⃣ **analytics_architecture_guide.md** (36 KB)
|
||||
**The Comprehensive Reference** — Read this first for deep understanding
|
||||
- ✅ DDD layer breakdown with code examples
|
||||
- ✅ All 24 endpoints documented
|
||||
- ✅ Query handler patterns (2 styles: @Cacheable vs manual)
|
||||
- ✅ Complete caching system (Redis patterns, TTLs, invalidation)
|
||||
- ✅ Real code examples from `GetMarketSnapshotHandler`, `GetDistrictStatsHandler`
|
||||
- ✅ Full Prisma schema for Property, Listing, MarketIndex, Valuation
|
||||
- ✅ Shared module utilities (CacheService, Cacheable decorator, interceptor)
|
||||
- ✅ **7-step guide: How to add GET /analytics/trending-areas endpoint**
|
||||
- ✅ Testing patterns
|
||||
- ✅ Error handling conventions
|
||||
|
||||
### 2️⃣ **quick_reference.md** (8 KB)
|
||||
**The Visual Quick Start** — Use this to navigate fast
|
||||
- ✅ Layer stack diagram (Presentation → Application → Domain → Infrastructure)
|
||||
- ✅ Request flow example (HTTP → Controller → QueryHandler → Cache → DB)
|
||||
- ✅ Caching strategy matrix (when to cache, TTLs, prefixes)
|
||||
- ✅ Decorators & guards cheat sheet
|
||||
- ✅ Prisma schema snapshot
|
||||
- ✅ Response structure with/without cache metadata
|
||||
- ✅ 7-step endpoint addition checklist
|
||||
|
||||
### 3️⃣ **file_paths_reference.md** (8 KB)
|
||||
**The Navigation Map** — Find files & understand structure
|
||||
- ✅ Core module files (analytics.module.ts, index.ts)
|
||||
- ✅ All 24 endpoints mapped to file paths
|
||||
- ✅ DTO files organized by type (request vs response)
|
||||
- ✅ All 15+ query types with descriptions
|
||||
- ✅ Domain, Infrastructure, and Shared layer breakdowns
|
||||
- ✅ Database schema models with fields & indexes
|
||||
- ✅ Directory tree with line counts
|
||||
- ✅ Import patterns reference
|
||||
- ✅ Key metrics & numbers
|
||||
|
||||
---
|
||||
|
||||
## ✨ Key Findings
|
||||
|
||||
### Architecture Pattern
|
||||
```
|
||||
Domain-Driven Design (DDD) + CQRS (Command Query Responsibility Segregation)
|
||||
4-layer structure: Presentation → Application → Domain → Infrastructure
|
||||
```
|
||||
|
||||
### Controllers (Entry Points)
|
||||
- **AnalyticsController**: 19 endpoints (`/analytics/...`)
|
||||
- **AvmController**: 5 endpoints (`/avm/...`)
|
||||
- **Total**: 24 endpoints, all with guards (JWT, Quota, Rate Limit)
|
||||
|
||||
### Query Handlers: 2 Caching Patterns
|
||||
|
||||
**Pattern 1: @Cacheable Decorator** (Simpler)
|
||||
```ts
|
||||
@QueryHandler(GetDistrictStatsQuery)
|
||||
export class GetDistrictStatsHandler {
|
||||
@Cacheable({
|
||||
prefix: CachePrefix.MARKET_DISTRICT,
|
||||
ttl: CacheTTL.DISTRICT_STATS,
|
||||
resource: 'district_stats',
|
||||
keyFrom: (query) => [query.city, query.period],
|
||||
})
|
||||
async execute(query): Promise<DistrictStatsDto> {
|
||||
return this.marketIndexRepo.getDistrictStats(query.city, query.period);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern 2: cache.getOrSet()** (For complex computation)
|
||||
```ts
|
||||
async execute(query): Promise<MarketSnapshotDto> {
|
||||
const cacheKey = CacheService.buildKey(CachePrefix.MARKET_SNAPSHOT, query.city);
|
||||
return await this.cache.getOrSet(
|
||||
cacheKey,
|
||||
() => this.computeSnapshot(query.city), // Heavy computation
|
||||
CacheTTL.MARKET_SNAPSHOT, // TTL in seconds
|
||||
'market_snapshot', // Prometheus label
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Redis Caching Strategy
|
||||
- **Cache-aside pattern**: Try Redis → if miss, call loader, store result
|
||||
- **Envelope format**: `{ __v: data, cachedAt: ISO, ttlSeconds: 300 }`
|
||||
- **Graceful degradation**: If Redis down, calls loader directly (no error)
|
||||
- **Metrics**: `cache_hit_total`, `cache_miss_total`, `cache_degradation_total`
|
||||
- **TTLs**: Dashboard=300s, Reports=900s, Trends=1800s, Predictions=NO_CACHE
|
||||
|
||||
### Prisma Schema
|
||||
- **Property**: id, type, address, district, city, location (PostGIS Point), area, rooms, etc.
|
||||
- **Listing**: id, propertyId, sellerId, status, priceVND (BigInt), aiPriceEstimate, publishedAt
|
||||
- **MarketIndex**: district, city, propertyType, period; medianPrice (BigInt), avgPriceM2, stats
|
||||
- **Valuation**: id, propertyId, estimatedPrice, confidence, method, features (Json)
|
||||
|
||||
### DDD Layers
|
||||
1. **Presentation**: Controllers, DTOs, Interceptors
|
||||
2. **Application**: Query/Command Handlers (@QueryHandler, @CommandHandler)
|
||||
3. **Domain**: Entities, Repository Interfaces, Service Interfaces
|
||||
4. **Infrastructure**: Prisma Repositories, HTTP Services, External clients
|
||||
|
||||
### Shared Module Utilities
|
||||
- **CacheService**: Core cache-aside with Redis
|
||||
- **@Cacheable**: Method decorator for handlers
|
||||
- **CacheMetaInterceptor**: Wraps responses with `{ data, cacheMeta }`
|
||||
- **LoggerService**: Winston-based logging
|
||||
- **PrismaService**: ORM wrapper
|
||||
- **RedisService**: Redis client wrapper
|
||||
- **Guards**: JWT, Quota, Rate Limit
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start: Adding New Endpoint
|
||||
|
||||
Example: **GET /analytics/trending-areas**
|
||||
|
||||
### 7-Step Process
|
||||
|
||||
1. **Request DTO** (`presentation/dto/get-trending-areas.dto.ts`)
|
||||
```ts
|
||||
export class GetTrendingAreasDto {
|
||||
@IsOptional() city?: string = 'Hồ Chí Minh';
|
||||
@IsOptional() propertyType?: PropertyType;
|
||||
@IsOptional() @Min(1) limit?: number = 10;
|
||||
}
|
||||
```
|
||||
|
||||
2. **Query Class** (`application/queries/get-trending-areas/get-trending-areas.query.ts`)
|
||||
```ts
|
||||
export class GetTrendingAreasQuery {
|
||||
constructor(
|
||||
public readonly city: string,
|
||||
public readonly propertyType: PropertyType | undefined,
|
||||
public readonly limit: number,
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Handler** (`application/queries/get-trending-areas/get-trending-areas.handler.ts`)
|
||||
```ts
|
||||
@QueryHandler(GetTrendingAreasQuery)
|
||||
export class GetTrendingAreasHandler implements IQueryHandler {
|
||||
@Cacheable({
|
||||
prefix: CachePrefix.TRENDING_AREAS,
|
||||
ttl: CacheTTL.TRENDING_AREAS,
|
||||
resource: 'trending_areas',
|
||||
keyFrom: (query) => [query.city, query.propertyType, query.limit],
|
||||
})
|
||||
async execute(query: GetTrendingAreasQuery): Promise<GetTrendingAreasDto> {
|
||||
// Your logic here
|
||||
return { city: query.city, areas: [...], cachedAt: null, nextRefreshAt: null };
|
||||
}
|
||||
}
|
||||
|
||||
export interface GetTrendingAreasDto {
|
||||
city: string;
|
||||
areas: TrendingAreaDto[];
|
||||
cachedAt: string | null;
|
||||
nextRefreshAt: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
4. **Register Handler** (in `analytics.module.ts`)
|
||||
```ts
|
||||
const QueryHandlers = [
|
||||
// ... existing
|
||||
GetTrendingAreasHandler, // Add here
|
||||
];
|
||||
```
|
||||
|
||||
5. **Controller Method** (in `analytics.controller.ts`)
|
||||
```ts
|
||||
@ApiBearerAuth('JWT')
|
||||
@UseGuards(JwtAuthGuard, QuotaGuard)
|
||||
@RequireQuota('analytics_queries')
|
||||
@Get('trending-areas')
|
||||
@ApiOperation({ summary: 'Get trending districts' })
|
||||
@ApiResponse({ status: 200, description: 'Trending areas retrieved' })
|
||||
async getTrendingAreas(@Query() dto: GetTrendingAreasDto): Promise<GetTrendingAreasDto> {
|
||||
return this.queryBus.execute(
|
||||
new GetTrendingAreasQuery(dto.city || 'Hồ Chí Minh', dto.propertyType, dto.limit || 10),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
6. **Export DTOs** (in `presentation/dto/index.ts`)
|
||||
```ts
|
||||
export * from './get-trending-areas.dto';
|
||||
```
|
||||
|
||||
7. **Test** (in `__tests__/get-trending-areas.handler.spec.ts`)
|
||||
```ts
|
||||
it('should return trending areas', async () => {
|
||||
const query = new GetTrendingAreasQuery('Hồ Chí Minh', undefined, 10);
|
||||
const result = await handler.execute(query);
|
||||
expect(result.areas).toBeDefined();
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Architecture Decision Points
|
||||
|
||||
| Decision | Current Approach | Why |
|
||||
|----------|------------------|-----|
|
||||
| **Caching** | Redis + cache-aside | TTL-based expiry is simple & performant |
|
||||
| **Cache Invalidation** | Prefix-based SCAN | Non-blocking, doesn't require key enumeration |
|
||||
| **Cache Metadata** | AsyncLocalStorage + Interceptor | Per-request context without global state |
|
||||
| **Query Patterns** | CQRS with QueryBus | Separates reads from writes, enables caching layer |
|
||||
| **Rate Limiting** | EndpointRateLimitGuard | Per-endpoint control, different rates for different ops |
|
||||
| **Quota Metering** | @RequireQuota decorator | Subscription-aware, tracks usage |
|
||||
| **Response Format** | DTO with cache metadata | Frontend knows freshness of data |
|
||||
| **Error Handling** | DomainException + InternalServerError | Differentiates logic errors from system errors |
|
||||
| **Graceful Degradation** | Cache bypass if Redis down | Service stays up during Redis maintenance |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Core Conventions to Remember
|
||||
|
||||
✅ **Always cache with TTL**
|
||||
- Dashboard tiles: 300s
|
||||
- Aggregations: 300s
|
||||
- Reports: 900s
|
||||
- Trends: 1800s
|
||||
- Predictions: NO CACHE (always fresh)
|
||||
|
||||
✅ **Always use CacheService.buildKey()**
|
||||
- Ensures deterministic, lowercase keys
|
||||
- Replaces spaces with underscores
|
||||
- Format: `prefix:param1:param2:param3`
|
||||
|
||||
✅ **Always wrap handlers in try-catch**
|
||||
```ts
|
||||
try {
|
||||
// logic
|
||||
} catch (error) {
|
||||
if (error instanceof DomainException) throw error;
|
||||
this.logger.error(...);
|
||||
throw new InternalServerErrorException('...');
|
||||
}
|
||||
```
|
||||
|
||||
✅ **Always return DTO with null metadata**
|
||||
```ts
|
||||
return {
|
||||
// ...data fields
|
||||
cachedAt: null, // Filled by CacheMetaInterceptor
|
||||
nextRefreshAt: null, // Filled by CacheMetaInterceptor
|
||||
};
|
||||
```
|
||||
|
||||
✅ **Always use @UseInterceptors(CacheMetaInterceptor)**
|
||||
- On controllers to wrap response
|
||||
- Adds: `{ data: T, cacheMeta: { cachedAt, nextRefreshAt, source } }`
|
||||
|
||||
✅ **Always add guards to endpoints**
|
||||
```ts
|
||||
@UseGuards(JwtAuthGuard, QuotaGuard) // Auth + quota check
|
||||
@RequireQuota('analytics_queries') // Meter usage
|
||||
@EndpointRateLimit({ limit: 10, windowSeconds: 60 }) // Rate limit if needed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 File Summary
|
||||
|
||||
| File Path | Purpose | Lines |
|
||||
|-----------|---------|-------|
|
||||
| `presentation/controllers/analytics.controller.ts` | Main endpoints | 331 |
|
||||
| `presentation/controllers/avm.controller.ts` | Valuation endpoints | 171 |
|
||||
| `application/queries/*/get-*.handler.ts` | Query execution + caching | 50-100 ea |
|
||||
| `domain/repositories/market-index.repository.ts` | Repository interface | 58 |
|
||||
| `infrastructure/repositories/prisma-market-index.repository.ts` | Prisma implementation | 150+ |
|
||||
| `presentation/interceptors/cache-meta.interceptor.ts` | Response wrapper | 61 |
|
||||
| `../shared/infrastructure/cache.service.ts` | Redis cache layer | 191 |
|
||||
| `../shared/infrastructure/decorators/cacheable.decorator.ts` | @Cacheable | 57 |
|
||||
| `analytics.module.ts` | NestJS module definition | 102 |
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Next Steps
|
||||
|
||||
1. **Read** → Start with `quick_reference.md` for visual understanding
|
||||
2. **Reference** → Use `file_paths_reference.md` to find specific files
|
||||
3. **Deep Dive** → Study `analytics_architecture_guide.md` for patterns & code
|
||||
4. **Build** → Follow the 7-step checklist to add your first endpoint
|
||||
5. **Test** → Create query handler spec following existing patterns
|
||||
|
||||
---
|
||||
|
||||
## Questions to Validate Understanding
|
||||
|
||||
After reading the guides, you should be able to answer:
|
||||
|
||||
1. What are the 4 DDD layers and what goes in each?
|
||||
2. How does the cache-aside pattern work when Redis is down?
|
||||
3. What's the difference between @Cacheable and cache.getOrSet()?
|
||||
4. Why do response DTOs have `cachedAt: null` and `nextRefreshAt: null`?
|
||||
5. How is the cache key built deterministically?
|
||||
6. What TTLs are used for different endpoint types?
|
||||
7. What guards are required on all analytics endpoints?
|
||||
8. How do you add a new GET endpoint in 7 steps?
|
||||
9. What's the purpose of CacheMetaInterceptor?
|
||||
10. How does graceful degradation work if Redis is unavailable?
|
||||
|
||||
---
|
||||
|
||||
1228
docs/explorations/from-desktop/01_analytics_architecture_guide.md
Normal file
1228
docs/explorations/from-desktop/01_analytics_architecture_guide.md
Normal file
File diff suppressed because it is too large
Load Diff
250
docs/explorations/from-desktop/02_quick_reference.md
Normal file
250
docs/explorations/from-desktop/02_quick_reference.md
Normal file
@@ -0,0 +1,250 @@
|
||||
# Quick Reference: Analytics Module Architecture
|
||||
|
||||
## 🏗️ Layer Stack (DDD + CQRS)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ PRESENTATION LAYER │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐│
|
||||
│ │ @Controller('analytics') / @Controller('avm') ││
|
||||
│ │ ├─ @Get endpoints (call QueryBus) ││
|
||||
│ │ ├─ @Post endpoints (call QueryBus or CommandBus) ││
|
||||
│ │ └─ Guards: JwtAuthGuard, QuotaGuard, EndpointRateLimitGuard ││
|
||||
│ ├─ DTOs: RequestDto, ResponseDto (validation) ││
|
||||
│ └─ Interceptors: CacheMetaInterceptor (wraps response) ││
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓ QueryBus.execute()
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ APPLICATION LAYER (CQRS) │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐│
|
||||
│ │ @QueryHandler(SomeQuery) ││
|
||||
│ │ ├─ Receives Query class instance ││
|
||||
│ │ ├─ Injects dependencies (Prisma, Cache, Logger) ││
|
||||
│ │ ├─ Caching: @Cacheable decorator OR cache.getOrSet() ││
|
||||
│ │ └─ Returns ResponseDto ││
|
||||
│ └─ Handlers indexed in analytics.module.ts ││
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓ Injected repos/services
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ DOMAIN LAYER (Business Logic) │
|
||||
│ ├─ Entities: MarketIndexEntity, ValuationEntity │
|
||||
│ ├─ Repository Interfaces: IMarketIndexRepository, etc. │
|
||||
│ ├─ Result DTOs: MarketReportResult, DistrictStatsResult │
|
||||
│ └─ Services: IAVMService, INeighborhoodScoreService │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓ Injected implementation
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ INFRASTRUCTURE LAYER │
|
||||
│ ├─ Repositories: PrismaMarketIndexRepository (implements iface) │
|
||||
│ ├─ Services: HttpAVMService, PrismaAVMService │
|
||||
│ └─ External: Prisma ORM, PostgreSQL, Redis, HTTP clients │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 📂 File Structure Quick Map
|
||||
|
||||
```
|
||||
apps/api/src/modules/analytics/
|
||||
├── presentation/
|
||||
│ ├── controllers/
|
||||
│ │ ├── analytics.controller.ts (14 GET/POST endpoints)
|
||||
│ │ └── avm.controller.ts (5 GET/POST endpoints)
|
||||
│ ├── dto/ (15+ DTO files)
|
||||
│ │ ├── get-market-snapshot.dto.ts
|
||||
│ │ ├── predict-valuation.dto.ts
|
||||
│ │ └── ...
|
||||
│ └── interceptors/
|
||||
│ └── cache-meta.interceptor.ts (wraps response)
|
||||
│
|
||||
├── application/
|
||||
│ ├── queries/ (15+ query types)
|
||||
│ │ ├── get-market-snapshot/
|
||||
│ │ │ ├── .query.ts (Q: GetMarketSnapshotQuery)
|
||||
│ │ │ └── .handler.ts (@QueryHandler + cache logic)
|
||||
│ │ ├── get-district-stats/
|
||||
│ │ ├── get-price-trend/
|
||||
│ │ ├── predict-valuation/
|
||||
│ │ └── ...
|
||||
│ ├── commands/ (3 command types)
|
||||
│ ├── event-handlers/ (1 event handler)
|
||||
│ └── queries/_shared/ (shared utilities)
|
||||
│
|
||||
├── domain/
|
||||
│ ├── entities/
|
||||
│ │ ├── market-index.entity.ts
|
||||
│ │ └── valuation.entity.ts
|
||||
│ ├── repositories/ (interfaces only)
|
||||
│ │ ├── market-index.repository.ts
|
||||
│ │ │ └── IMarketIndexRepository interface
|
||||
│ │ └── valuation.repository.ts
|
||||
│ ├── services/ (interfaces only)
|
||||
│ │ ├── avm-service.ts
|
||||
│ │ └── neighborhood-score.service.ts
|
||||
│ └── events/
|
||||
│ └── market-index-updated.event.ts
|
||||
│
|
||||
├── infrastructure/
|
||||
│ ├── repositories/
|
||||
│ │ ├── prisma-market-index.repository.ts (implements)
|
||||
│ │ └── prisma-valuation.repository.ts (implements)
|
||||
│ └── services/
|
||||
│ ├── http-avm.service.ts (calls Python AI)
|
||||
│ ├── prisma-avm.service.ts (fallback)
|
||||
│ ├── http-neighborhood-score.service.ts
|
||||
│ ├── prisma-neighborhood-score.service.ts
|
||||
│ ├── ai-service.client.ts (Claude API)
|
||||
│ └── market-index-cron.service.ts
|
||||
│
|
||||
├── analytics.module.ts (NestJS module, registers all)
|
||||
├── index.ts (exports)
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 🔄 Request Flow Example
|
||||
|
||||
```
|
||||
HTTP GET /analytics/market-snapshot?city=Ho Chi Minh
|
||||
|
||||
1. AnalyticsController.getMarketSnapshot(@Query dto)
|
||||
└─ Validates DTO (class-validator)
|
||||
└─ Calls queryBus.execute(new GetMarketSnapshotQuery(...))
|
||||
|
||||
2. QueryBus routes to GetMarketSnapshotHandler
|
||||
└─ Handler caches key: "cache:analytics:market_snapshot:ho_chi_minh"
|
||||
└─ Calls cache.getOrSet(key, computeSnapshot, 300s, 'market_snapshot')
|
||||
|
||||
3. CacheService.getOrSet():
|
||||
├─ IF Redis HIT: return cached value, increment cache_hit_total
|
||||
└─ IF MISS: call computeSnapshot(), store in Redis, increment cache_miss_total
|
||||
|
||||
4. GetMarketSnapshotHandler.computeSnapshot():
|
||||
├─ Parallel queries:
|
||||
│ ├─ listing.aggregate() → count, avg price
|
||||
│ ├─ $queryRaw PERCENTILE_CONT → median
|
||||
│ ├─ $queryRaw AVG(EXTRACT...) → days on market
|
||||
│ └─ computePriceChangePct (3x for 1d/7d/30d)
|
||||
└─ Returns MarketSnapshotDto
|
||||
|
||||
5. CacheMetaInterceptor wraps response:
|
||||
└─ Transforms: MarketSnapshotDto
|
||||
└─ Into: { data: MarketSnapshotDto, cacheMeta: { cachedAt, nextRefreshAt, source } }
|
||||
|
||||
6. HTTP 200 with wrapped response
|
||||
```
|
||||
|
||||
## 💾 Caching Strategy
|
||||
|
||||
### When to Cache
|
||||
```
|
||||
✅ Dashboard tiles → 300s TTL (5 min)
|
||||
✅ Aggregations (district stats) → 300s TTL
|
||||
✅ Market reports → 900s TTL (15 min)
|
||||
✅ Historical trends → 1800s TTL (30 min)
|
||||
❌ AI predictions → NO CACHE (always fresh)
|
||||
❌ User-specific data → NO CACHE (personalized)
|
||||
```
|
||||
|
||||
### Cache Prefix Patterns
|
||||
```
|
||||
Prefix Example Key
|
||||
────────────────────────────────────────────────────────
|
||||
MARKET_SNAPSHOT cache:analytics:market_snapshot:ho_chi_minh
|
||||
MARKET_DISTRICT cache:market:district:ho_chi_minh:2024_q1
|
||||
MARKET_TREND cache:market:trend:q1:ho_chi_minh:apartment:...
|
||||
TRENDING_AREAS cache:analytics:trending_areas:ho_chi_minh:...
|
||||
VALUATION cache:valuation:prop_123
|
||||
```
|
||||
|
||||
### Cache Invalidation
|
||||
```
|
||||
Automatic: Redis TTL expires
|
||||
Manual: cache.invalidateByPrefix(CachePrefix.MARKET_DISTRICT)
|
||||
Scans "cache:market:district:*" and deletes all matches
|
||||
```
|
||||
|
||||
## 🛡️ Decorators & Guards
|
||||
|
||||
```
|
||||
┌─ @ApiBearerAuth('JWT') ← Swagger annotation
|
||||
├─ @UseGuards(JwtAuthGuard) ← Requires JWT token
|
||||
├─ @UseGuards(QuotaGuard) ← Checks subscription quota
|
||||
├─ @RequireQuota('analytics_queries') ← Meters usage
|
||||
├─ @EndpointRateLimit({ limit: 10, windowSeconds: 60 })
|
||||
├─ @UseGuards(EndpointRateLimitGuard) ← Rate limiter
|
||||
├─ @UseInterceptors(CacheMetaInterceptor) ← Wraps response
|
||||
└─ @ApiOperation({ summary: '...' })
|
||||
@ApiResponse({ status: 200, description: '...' })
|
||||
@ApiResponse({ status: 403, description: 'Quota exceeded' })
|
||||
(Swagger documentation)
|
||||
```
|
||||
|
||||
## 📊 Prisma Schema Snapshot
|
||||
|
||||
```
|
||||
Property
|
||||
├─ id, propertyType (APARTMENT, VILLA, ...)
|
||||
├─ address, ward, district, city
|
||||
├─ location (PostGIS geometry Point)
|
||||
├─ areaM2, bedrooms, bathrooms, floors
|
||||
├─ yearBuilt, furnishing, condition
|
||||
├─ createdAt, updatedAt
|
||||
└─ Indexes: [propertyType], [district, city], [location (Gist)]
|
||||
|
||||
Listing
|
||||
├─ id, propertyId (FK), sellerId (FK)
|
||||
├─ transactionType (SALE, RENT)
|
||||
├─ status (ACTIVE, SOLD, EXPIRED, ...)
|
||||
├─ priceVND (BigInt, CHECK > 0)
|
||||
├─ pricePerM2, aiPriceEstimate, aiConfidence
|
||||
├─ publishedAt, expiresAt, createdAt
|
||||
└─ Indexes: [status], [sellerId, status], [status, publishedAt]
|
||||
|
||||
MarketIndex
|
||||
├─ district, city, propertyType, period (2024-Q1)
|
||||
├─ medianPrice (BigInt), avgPriceM2
|
||||
├─ totalListings, daysOnMarket
|
||||
├─ inventoryLevel, absorptionRate, yoyChange
|
||||
└─ Unique: [district, city, propertyType, period]
|
||||
```
|
||||
|
||||
## 🎯 Response Structure
|
||||
|
||||
```
|
||||
WITH CacheMetaInterceptor (@UseInterceptors):
|
||||
{
|
||||
"data": {
|
||||
"city": "Hồ Chí Minh",
|
||||
"activeCount": 2500,
|
||||
...
|
||||
},
|
||||
"cacheMeta": {
|
||||
"cachedAt": "2024-04-21T10:30:00Z",
|
||||
"nextRefreshAt": "2024-04-21T10:35:00Z",
|
||||
"source": "cache"
|
||||
}
|
||||
}
|
||||
|
||||
WITHOUT interceptor (plain DTO):
|
||||
{
|
||||
"city": "Hồ Chí Minh",
|
||||
"activeCount": 2500,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 Adding Endpoint: 7-Step Checklist
|
||||
|
||||
- [ ] Create Request DTO: `presentation/dto/get-*.dto.ts`
|
||||
- [ ] Create Query: `application/queries/get-*/get-*.query.ts`
|
||||
- [ ] Create Handler: `application/queries/get-*/get-*.handler.ts`
|
||||
- [ ] @QueryHandler decorator
|
||||
- [ ] @Cacheable or cache.getOrSet()
|
||||
- [ ] Try-catch with logger
|
||||
- [ ] Update module: Add handler to QueryHandlers array
|
||||
- [ ] Add controller method: `analytics.controller.ts` or `avm.controller.ts`
|
||||
- [ ] @Get or @Post
|
||||
- [ ] Guards & decorators (@UseGuards, @RequireQuota, etc.)
|
||||
- [ ] Swagger annotations (@ApiOperation, @ApiResponse)
|
||||
- [ ] Export DTOs from `presentation/dto/index.ts`
|
||||
- [ ] Test: Query handler test, integration test
|
||||
|
||||
384
docs/explorations/from-desktop/03_file_paths_reference.md
Normal file
384
docs/explorations/from-desktop/03_file_paths_reference.md
Normal file
@@ -0,0 +1,384 @@
|
||||
# Analytics Module — File Paths & Quick Reference
|
||||
|
||||
## 🔗 Core Module Files
|
||||
|
||||
```
|
||||
/apps/api/src/modules/analytics/
|
||||
│
|
||||
├── analytics.module.ts
|
||||
│ └─ Registers all handlers, repositories, services
|
||||
│ └─ Module metadata: imports, controllers, providers, exports
|
||||
│ └─ KEY: CommandHandlers, QueryHandlers, EventHandlers arrays
|
||||
│
|
||||
├── index.ts
|
||||
│ └─ Public exports of analytics module
|
||||
│
|
||||
└── README.md
|
||||
└─ Module documentation
|
||||
```
|
||||
|
||||
## 🎯 Controllers (Entry Points)
|
||||
|
||||
```
|
||||
/apps/api/src/modules/analytics/presentation/controllers/
|
||||
|
||||
analytics.controller.ts (19 endpoints)
|
||||
├─ GET /analytics/market-report
|
||||
├─ GET /analytics/market-snapshot
|
||||
├─ GET /analytics/price-trend
|
||||
├─ GET /analytics/heatmap
|
||||
├─ GET /analytics/district-stats
|
||||
├─ GET /analytics/valuation (query param)
|
||||
├─ POST /analytics/valuation (body)
|
||||
├─ POST /analytics/valuation/batch
|
||||
├─ GET /analytics/valuation/history/:propertyId
|
||||
├─ POST /analytics/valuation/compare
|
||||
├─ GET /analytics/neighborhoods/:district/score
|
||||
├─ GET /analytics/pois/nearby
|
||||
├─ POST /analytics/listings/:id/ai-advice
|
||||
└─ POST /analytics/projects/:id/ai-advice
|
||||
|
||||
avm.controller.ts (5 endpoints)
|
||||
├─ POST /avm/batch
|
||||
├─ GET /avm/history/:propertyId
|
||||
├─ GET /avm/compare?ids=...
|
||||
├─ GET /avm/explain?valuationId=...
|
||||
└─ POST /avm/industrial
|
||||
```
|
||||
|
||||
## 📋 DTOs (Requests & Responses)
|
||||
|
||||
```
|
||||
/apps/api/src/modules/analytics/presentation/dto/
|
||||
|
||||
REQUEST DTOs (from @Query/@Body):
|
||||
├─ get-district-stats.dto.ts (city, period)
|
||||
├─ get-heatmap.dto.ts (city, period)
|
||||
├─ get-market-report.dto.ts (city, period, propertyType)
|
||||
├─ get-market-snapshot.dto.ts (city, propertyType)
|
||||
├─ get-price-trend.dto.ts (district, city, propertyType, periods)
|
||||
├─ get-valuation.dto.ts (propertyId | lat/lng/areaM2)
|
||||
├─ get-nearby-pois.dto.ts (lat, lng, radius, limit)
|
||||
├─ predict-valuation.dto.ts (20+ fields, v1 & v2)
|
||||
├─ batch-valuation.dto.ts (propertyIds: string[])
|
||||
├─ valuation-history.dto.ts (limit)
|
||||
├─ valuation-comparison.dto.ts (propertyIds)
|
||||
├─ avm-compare-query.dto.ts (ids)
|
||||
├─ avm-explain-query.dto.ts (valuationId)
|
||||
├─ industrial-valuation.dto.ts (30+ industrial fields)
|
||||
└─ get-trending-areas.dto.ts (city, propertyType, limit, period)
|
||||
|
||||
RESPONSE DTOs (exported from handlers):
|
||||
(See handler files below)
|
||||
```
|
||||
|
||||
## 🔄 Queries (CQRS Pattern)
|
||||
|
||||
```
|
||||
/apps/api/src/modules/analytics/application/queries/
|
||||
|
||||
STRUCTURE OF EACH QUERY TYPE:
|
||||
get-market-snapshot/
|
||||
├─ get-market-snapshot.query.ts
|
||||
│ └─ export class GetMarketSnapshotQuery { ... }
|
||||
│
|
||||
└─ get-market-snapshot.handler.ts
|
||||
├─ @QueryHandler(GetMarketSnapshotQuery)
|
||||
├─ execute(query): Promise<MarketSnapshotDto>
|
||||
└─ export interface MarketSnapshotDto { ... }
|
||||
|
||||
ALL QUERY TYPES (15+):
|
||||
├─ get-market-snapshot/ ..................... Dashboard overview
|
||||
├─ get-district-stats/ ..................... Stats aggregation
|
||||
├─ get-price-trend/ ........................ Time-series data
|
||||
├─ get-heatmap/ ........................... Geographic visualization
|
||||
├─ get-valuation/ ......................... Single valuation
|
||||
├─ predict-valuation/ ..................... AI prediction
|
||||
├─ batch-valuation/ ....................... Multiple valuations
|
||||
├─ valuation-history/ ..................... Time-series valuations
|
||||
├─ valuation-comparison/ .................. Side-by-side comparison
|
||||
├─ valuation-explanation/ ................. Model drivers
|
||||
├─ get-neighborhood-score/ ................ Neighborhood quality
|
||||
├─ get-nearby-pois/ ....................... Point of interests
|
||||
├─ get-listing-ai-advice/ ................. Claude analysis
|
||||
├─ get-project-ai-advice/ ................. Project analysis
|
||||
├─ industrial-valuation/ .................. Industrial rent
|
||||
├─ get-market-report/ ..................... Detailed report
|
||||
└─ get-trending-areas/ .................... Trending districts
|
||||
```
|
||||
|
||||
## 🏛️ Domain Layer
|
||||
|
||||
```
|
||||
/apps/api/src/modules/analytics/domain/
|
||||
|
||||
repositories/ (Interfaces only — NO IMPLEMENTATION)
|
||||
├─ market-index.repository.ts
|
||||
│ ├─ export const MARKET_INDEX_REPOSITORY = Symbol(...)
|
||||
│ ├─ export interface IMarketIndexRepository {
|
||||
│ │ findById(id)
|
||||
│ │ findByKey(district, city, propertyType, period)
|
||||
│ │ save(entity)
|
||||
│ │ update(entity)
|
||||
│ │ getMarketReport(city, period, propertyType?)
|
||||
│ │ getHeatmap(city, period)
|
||||
│ │ getPriceTrend(district, city, propertyType, periods)
|
||||
│ │ getDistrictStats(city, period)
|
||||
│ │ }
|
||||
│ └─ Result interfaces: MarketReportResult, HeatmapDataPoint, etc.
|
||||
│
|
||||
└─ valuation.repository.ts
|
||||
├─ export const VALUATION_REPOSITORY = Symbol(...)
|
||||
└─ export interface IValuationRepository { ... }
|
||||
|
||||
entities/
|
||||
├─ market-index.entity.ts
|
||||
│ └─ Domain logic for market data aggregation
|
||||
│
|
||||
└─ valuation.entity.ts
|
||||
└─ Domain logic for property valuation
|
||||
|
||||
services/
|
||||
├─ avm-service.ts
|
||||
│ └─ export const AVM_SERVICE = Symbol(...)
|
||||
│ └─ IAVMService interface: predict(), getComparables(), etc.
|
||||
│
|
||||
└─ neighborhood-score.service.ts
|
||||
└─ INeighborhoodScoreService interface
|
||||
|
||||
events/
|
||||
└─ market-index-updated.event.ts
|
||||
└─ Domain event when market data updates
|
||||
```
|
||||
|
||||
## 🔧 Infrastructure Layer
|
||||
|
||||
```
|
||||
/apps/api/src/modules/analytics/infrastructure/
|
||||
|
||||
repositories/ (IMPLEMENTATIONS)
|
||||
├─ prisma-market-index.repository.ts
|
||||
│ ├─ @Injectable()
|
||||
│ └─ class PrismaMarketIndexRepository implements IMarketIndexRepository {
|
||||
│ └─ Uses PrismaService for data access
|
||||
│
|
||||
└─ prisma-valuation.repository.ts
|
||||
└─ class PrismaValuationRepository implements IValuationRepository
|
||||
|
||||
services/ (IMPLEMENTATIONS)
|
||||
├─ http-avm.service.ts
|
||||
│ ├─ @Injectable()
|
||||
│ ├─ Calls Python AI service (HTTP client)
|
||||
│ └─ Falls back to PrismaAVMService if Python is down
|
||||
│
|
||||
├─ prisma-avm.service.ts
|
||||
│ ├─ @Injectable()
|
||||
│ └─ Fallback ML model using Prisma data
|
||||
│
|
||||
├─ http-neighborhood-score.service.ts
|
||||
│ ├─ HTTP proxy to external scoring service
|
||||
│ └─ Falls back to PrismaNeighborhoodScoreService
|
||||
│
|
||||
├─ prisma-neighborhood-score.service.ts
|
||||
│ └─ In-DB scoring logic
|
||||
│
|
||||
├─ ai-service.client.ts
|
||||
│ ├─ Wrapper around Anthropic SDK
|
||||
│ └─ Calls Claude API for AI analysis
|
||||
│
|
||||
└─ market-index-cron.service.ts
|
||||
└─ Scheduled job to update MarketIndex table
|
||||
```
|
||||
|
||||
## 🎨 Interceptors
|
||||
|
||||
```
|
||||
/apps/api/src/modules/analytics/presentation/interceptors/
|
||||
|
||||
cache-meta.interceptor.ts
|
||||
├─ @Injectable() CacheMetaInterceptor
|
||||
├─ Wraps response: T => { data: T; cacheMeta: {...} }
|
||||
├─ cacheMeta includes: cachedAt, nextRefreshAt, source
|
||||
└─ Applied via @UseInterceptors(CacheMetaInterceptor)
|
||||
```
|
||||
|
||||
## 📦 Shared Module (Reusable Utilities)
|
||||
|
||||
```
|
||||
/apps/api/src/modules/shared/
|
||||
|
||||
infrastructure/
|
||||
|
||||
cache.service.ts
|
||||
├─ @Injectable() CacheService
|
||||
├─ async getOrSet<T>(key, loader, ttl, resource)
|
||||
│ └─ Cache-aside pattern
|
||||
│ └─ Metrics: cache_hit_total, cache_miss_total, cache_degradation_total
|
||||
├─ async invalidate(key)
|
||||
├─ async invalidateByPrefix(prefix)
|
||||
├─ static buildKey(prefix, ...parts)
|
||||
└─ Graceful degradation when Redis is down
|
||||
|
||||
decorators/
|
||||
├─ cacheable.decorator.ts
|
||||
│ ├─ @Cacheable(options) method decorator
|
||||
│ └─ Declarative caching for query handlers
|
||||
│
|
||||
└─ other decorators...
|
||||
|
||||
cache-meta.store.ts
|
||||
├─ export const cacheMetaStorage = new AsyncLocalStorage()
|
||||
└─ Per-request storage of cache metadata
|
||||
|
||||
logger.service.ts
|
||||
├─ @Injectable() LoggerService
|
||||
├─ log(), warn(), error() with context
|
||||
└─ Winston integration
|
||||
|
||||
prisma.service.ts
|
||||
├─ @Injectable() PrismaService
|
||||
├─ Wrapper around Prisma Client
|
||||
└─ Handles connection lifecycle
|
||||
|
||||
redis.service.ts
|
||||
├─ @Injectable() RedisService
|
||||
├─ get(), set(), del(), scan()
|
||||
└─ Health check & graceful degradation
|
||||
|
||||
guards/
|
||||
├─ endpoint-rate-limit.guard.ts
|
||||
├─ ... other guards
|
||||
└─ auth module exports JwtAuthGuard
|
||||
|
||||
shared.module.ts
|
||||
└─ Registers all shared services
|
||||
```
|
||||
|
||||
## 📊 Database Schema
|
||||
|
||||
```
|
||||
prisma/schema.prisma
|
||||
|
||||
Models relevant to analytics:
|
||||
├─ Property
|
||||
│ ├─ id, propertyType, address, district, city
|
||||
│ ├─ location (PostGIS Point)
|
||||
│ ├─ areaM2, bedrooms, bathrooms, floors
|
||||
│ └─ Indexes: [propertyType], [district, city], [location]
|
||||
│
|
||||
├─ Listing
|
||||
│ ├─ id, propertyId (FK), sellerId (FK)
|
||||
│ ├─ status (ACTIVE, SOLD, EXPIRED, ...)
|
||||
│ ├─ priceVND (BigInt), pricePerM2, publishedAt
|
||||
│ ├─ aiPriceEstimate, aiConfidence (for AVM)
|
||||
│ └─ Indexes: [status], [sellerId, status], [publishedAt]
|
||||
│
|
||||
├─ MarketIndex
|
||||
│ ├─ district, city, propertyType, period
|
||||
│ ├─ medianPrice (BigInt), avgPriceM2
|
||||
│ ├─ totalListings, daysOnMarket, inventoryLevel
|
||||
│ └─ Unique: [district, city, propertyType, period]
|
||||
│
|
||||
├─ Valuation
|
||||
│ ├─ id, propertyId (FK)
|
||||
│ ├─ estimatedPrice (BigInt), confidence
|
||||
│ ├─ method (AVM_v1, AVM_v2, MANUAL)
|
||||
│ ├─ features (Json), comparables (Json), explainers (Json)
|
||||
│ └─ Index: [propertyId, valuationDate DESC]
|
||||
│
|
||||
└─ ProjectDevelopment
|
||||
├─ id, slug, developer
|
||||
├─ location (PostGIS Point)
|
||||
├─ minPrice, maxPrice, pricePerM2Range
|
||||
└─ Index: [district, city], [location]
|
||||
```
|
||||
|
||||
## 🌳 Directory Tree Summary
|
||||
|
||||
```
|
||||
goodgo-platform-ai/
|
||||
└─ apps/api/src/modules/
|
||||
├─ analytics/ (this module) .................. ~2000 LOC
|
||||
│ ├─ presentation/
|
||||
│ │ ├─ controllers/
|
||||
│ │ │ ├─ analytics.controller.ts ......... 331 lines
|
||||
│ │ │ └─ avm.controller.ts .............. 171 lines
|
||||
│ │ ├─ dto/ (15+ files)
|
||||
│ │ └─ interceptors/
|
||||
│ │ └─ cache-meta.interceptor.ts ....... 61 lines
|
||||
│ ├─ application/
|
||||
│ │ ├─ queries/ (15+ handlers)
|
||||
│ │ ├─ commands/ (3 handlers)
|
||||
│ │ └─ event-handlers/
|
||||
│ ├─ domain/
|
||||
│ │ ├─ repositories/ (2 interfaces)
|
||||
│ │ ├─ entities/ (2 entities)
|
||||
│ │ ├─ services/ (2 interfaces)
|
||||
│ │ └─ events/
|
||||
│ ├─ infrastructure/
|
||||
│ │ ├─ repositories/ (2 implementations)
|
||||
│ │ └─ services/ (6 implementations)
|
||||
│ └─ analytics.module.ts
|
||||
│
|
||||
├─ shared/ ............................. Reusable utilities
|
||||
│ ├─ infrastructure/
|
||||
│ │ ├─ cache.service.ts ............... 191 lines (core pattern)
|
||||
│ │ ├─ decorators/
|
||||
│ │ │ └─ cacheable.decorator.ts ....... 57 lines
|
||||
│ │ ├─ cache-meta.store.ts
|
||||
│ │ ├─ logger.service.ts
|
||||
│ │ ├─ prisma.service.ts
|
||||
│ │ ├─ redis.service.ts
|
||||
│ │ └─ guards/ (rate limit, auth, etc)
|
||||
│ └─ shared.module.ts
|
||||
│
|
||||
├─ auth/ ............................. Authentication
|
||||
├─ subscriptions/ ..................... Quota & billing
|
||||
├─ listings/ ......................... Listing management
|
||||
└─ [other modules]
|
||||
```
|
||||
|
||||
## 🔑 Key Imports Pattern
|
||||
|
||||
```ts
|
||||
// In analytics.controller.ts
|
||||
import { QueryBus } from '@nestjs/cqrs';
|
||||
import { JwtAuthGuard } from '@modules/auth';
|
||||
import { EndpointRateLimit, EndpointRateLimitGuard } from '@modules/shared';
|
||||
import { RequireQuota, QuotaGuard } from '@modules/subscriptions';
|
||||
|
||||
// In query handlers
|
||||
import {
|
||||
DomainException,
|
||||
CacheService,
|
||||
CachePrefix,
|
||||
CacheTTL,
|
||||
Cacheable,
|
||||
LoggerService,
|
||||
PrismaService,
|
||||
} from '@modules/shared';
|
||||
import { MARKET_INDEX_REPOSITORY, type IMarketIndexRepository } from '../../domain/...';
|
||||
|
||||
// In infrastructure repositories
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '@modules/shared';
|
||||
import { type IMarketIndexRepository } from '../../domain/...';
|
||||
```
|
||||
|
||||
## 🚀 Key Numbers
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Controllers | 2 |
|
||||
| Endpoints | 24 |
|
||||
| Query Handlers | 15+ |
|
||||
| DTOs | 15+ |
|
||||
| Repository Interfaces | 2 |
|
||||
| Repository Implementations | 2 |
|
||||
| Services (interfaces) | 2 |
|
||||
| Services (implementations) | 6+ |
|
||||
| Cache Prefixes | 10+ |
|
||||
| Cache TTLs | 20+ |
|
||||
| Total LOC (analytics module) | ~2000 |
|
||||
| Total LOC (shared/cache) | ~250 |
|
||||
|
||||
379
docs/explorations/from-desktop/ARCHITECTURE_OVERVIEW.txt
Normal file
379
docs/explorations/from-desktop/ARCHITECTURE_OVERVIEW.txt
Normal file
@@ -0,0 +1,379 @@
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ GOODGO FRONTEND ARCHITECTURE & DATA FLOW DIAGRAM ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ROUTING & LAYOUT HIERARCHY │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
app/
|
||||
└── layout.tsx (global)
|
||||
└── [locale]/
|
||||
├── layout.tsx (i18n wrapper)
|
||||
│
|
||||
├── (public)/ ─────────────────── PUBLIC ROUTES
|
||||
│ ├── listings/
|
||||
│ │ ├── page.tsx ────────────────────────────────┐
|
||||
│ │ └── [id]/page.tsx (Server RSC) ────────────┐ │
|
||||
│ │ └── <ListingDetailClient/> (Client) ─┐ │ │
|
||||
│ │ ├── <NeighborhoodRadarChart/> ──┐├─┼─┤ Neighborhood
|
||||
│ │ ├── <NeighborhoodPOIMap/> │ │ │
|
||||
│ │ └── <PriceHistoryChart/> ├─┘ │
|
||||
│ ├── search/
|
||||
│ ├── khu-cong-nghiep/
|
||||
│ └── du-an/
|
||||
│
|
||||
├── (auth)/ ──────────────────── AUTH ROUTES
|
||||
│ ├── login/
|
||||
│ └── register/
|
||||
│
|
||||
├── (dashboard)/ ──────────────── PROTECTED ROUTES
|
||||
│ ├── dashboard/
|
||||
│ ├── analytics/
|
||||
│ ├── valuation/
|
||||
│ ├── industrial-parks/
|
||||
│ └── reports/
|
||||
│
|
||||
└── (admin)/ ──────────────────── ADMIN ROUTES
|
||||
└── admin/
|
||||
├── users/
|
||||
├── kyc/
|
||||
└── moderation/
|
||||
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ LISTING DETAIL PAGE FLOW │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ /[locale]/(public)/listings/[id]/page.tsx (Server RSC) │
|
||||
│ ────────────────────────────────────────────────────────── │
|
||||
│ • generateMetadata() → SEO, OG, schema.json │
|
||||
│ • await fetchListingById(id) → Backend API │
|
||||
│ • Returns: <ListingDetailClient data={...} /> │
|
||||
└────┬───────────────────────────────────────────────────────┬┘
|
||||
│ SSR data injection (props) │
|
||||
▼ ▼
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ <ListingDetailClient/> (Client Component, 39 KB) │
|
||||
│ ───────────────────────────────────────────────────────── │
|
||||
│ Layout Sections (top to bottom): │
|
||||
│ │
|
||||
│ 1. IMAGE GALLERY ─────────── <ImageGallery /> │
|
||||
│ │
|
||||
│ 2. KPI STRIP ─────────────── Price | m² | DOM | Views │
|
||||
│ │
|
||||
│ 3. CORE DETAILS ──────────── Address, Bedrooms, etc. │
|
||||
│ │
|
||||
│ 4. PRICE HISTORY ─────────── <PriceHistoryChart /> │
|
||||
│ (Recharts AreaChart) │
|
||||
│ │
|
||||
│ 5. NEIGHBORHOOD SECTION ──── │
|
||||
│ ├─ <NeighborhoodRadarChart/> │
|
||||
│ │ (6 categories, 0-10 scores) │
|
||||
│ │ Dynamically loaded, SSR: false │
|
||||
│ │ │
|
||||
│ └─ <NeighborhoodPOIMap/> │
|
||||
│ (Mapbox GL with 6 POI types) │
|
||||
│ Dynamically loaded, SSR: false │
|
||||
│ • Schools, Hospitals, Transit, Shopping, etc. │
|
||||
│ • Category filters │
|
||||
│ • Distance display │
|
||||
│ │
|
||||
│ 6. AI ADVICE CARDS ────────── <AiAdviceCards /> │
|
||||
│ (Personas) │
|
||||
│ │
|
||||
│ 7. SIMILAR LISTINGS ───────── Recommendations │
|
||||
│ │
|
||||
│ 8. AGENT CARD ────────────── Agent quality score │
|
||||
│ │
|
||||
│ 9. CTA SECTION ────────────── Inquiry | Compare | Estimate │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ API & STATE MANAGEMENT DATA FLOW │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────┐
|
||||
│ Backend API │
|
||||
│ (api/v1/...) │
|
||||
└────────┬────────────┘
|
||||
│ HTTPS + CSRF Token
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────┐
|
||||
│ lib/api-client.ts │
|
||||
│ ───────────────────────────────────── │
|
||||
│ • Base URL: NEXT_PUBLIC_API_URL │
|
||||
│ • CSRF handling (from cookies) │
|
||||
│ • 401 auto-refresh + retry │
|
||||
│ • Type-safe generics: <T> │
|
||||
└────┬──────────────────────────────────┬──┘
|
||||
│ │
|
||||
├─ apiClient.get<T>() │ HTTP Methods
|
||||
├─ apiClient.post<T>() │
|
||||
├─ apiClient.patch<T>() │
|
||||
└─ apiClient.delete<T>() │
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ Domain-Specific API Clients │
|
||||
│ ───────────────────────────────────────── │
|
||||
│ • listings-api.ts → ListingDetail │
|
||||
│ • analytics-api.ts → MarketReport │
|
||||
│ • valuation-api.ts → AVM estimates │
|
||||
│ • agents-api.ts → AgentQualityScore │
|
||||
│ • khu-cong-nghiep-api.ts │
|
||||
│ • du-an-api.ts │
|
||||
│ • inquiries-api.ts │
|
||||
└────┬─────────────────────────────────────────┘
|
||||
│ Returns typed data (interfaces)
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ React Query (lib/hooks/) │
|
||||
│ ───────────────────────────────────────── │
|
||||
│ useQuery() with query keys: │
|
||||
│ │
|
||||
│ • useMarketReport(city, period) │
|
||||
│ • usePriceTrend(district, city, type) │
|
||||
│ • useListing(id) │
|
||||
│ • useIndustrialParks() │
|
||||
│ • useValuation() │
|
||||
│ │
|
||||
│ Features: │
|
||||
│ ✓ Automatic caching + deduplication │
|
||||
│ ✓ Background refetch intervals │
|
||||
│ ✓ Error boundaries │
|
||||
│ ✓ Loading states │
|
||||
└────┬──────────────────────────────────────────┬┘
|
||||
│ Provides data │
|
||||
│ & loading/error states │
|
||||
│ │
|
||||
├──────────────────┬───────────────────────┤
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
Components Zustand Stores React Context
|
||||
(render data) (client state) (global features)
|
||||
|
||||
• Listings • auth-store • QueryProvider
|
||||
• Charts • comparison-store • ThemeProvider
|
||||
• Maps • preferences-store • AuthProvider
|
||||
• Neighborhood • NotificationsProvider
|
||||
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ NEIGHBORHOOD COMPONENTS DETAIL │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
API Response (from backend)
|
||||
│
|
||||
├─ NeighborhoodScoreData
|
||||
│ ├─ overallScore: number (0-100)
|
||||
│ ├─ categories: NeighborhoodCategory[]
|
||||
│ │ └─ [
|
||||
│ │ { category: "education", label: "Giáo dục", score: 7.5 },
|
||||
│ │ { category: "healthcare", label: "Y tế", score: 8.2 },
|
||||
│ │ { category: "transport", label: "Giao thông", score: 6.8 },
|
||||
│ │ { category: "shopping", label: "Mua sắm", score: 7.9 },
|
||||
│ │ { category: "dining", label: "Ẩm thực", score: 8.1 },
|
||||
│ │ { category: "environment", label: "Môi trường", score: 7.3 },
|
||||
│ │ ]
|
||||
│ ├─ pois: POIItem[]
|
||||
│ │ └─ [
|
||||
│ │ { id: "1", name: "School X", category: "school", lat: 10.123, lng: 105.456, distance: 500 },
|
||||
│ │ { id: "2", name: "Hospital Y", category: "hospital", lat: 10.124, lng: 105.457, distance: 800 },
|
||||
│ │ ...
|
||||
│ │ ]
|
||||
│ └─ center: { lat: 10.123, lng: 105.456 }
|
||||
│
|
||||
└─ Rendered as:
|
||||
│
|
||||
├─────────────────────────────────────────────┐
|
||||
│ <NeighborhoodRadarChart/> │
|
||||
│ ───────────────────────────────────────── │
|
||||
│ Recharts RadarChart Component │
|
||||
│ │
|
||||
│ Input: categories (0-10 scores) │
|
||||
│ Output: │
|
||||
│ • Radar polygon visualization │
|
||||
│ • Badge strips below (Tốt/TB/Yếu) │
|
||||
│ • Dark/light theme CSS variables │
|
||||
└─────────────────────────────────────────────┘
|
||||
│
|
||||
├─────────────────────────────────────────────┐
|
||||
│ <NeighborhoodPOIMap/> │
|
||||
│ ───────────────────────────────────────── │
|
||||
│ Mapbox GL Component (Client) │
|
||||
│ │
|
||||
│ Features: │
|
||||
│ • Displays 6 POI category types │
|
||||
│ • SVG icon markers (school, hospital, etc.)│
|
||||
│ • Category filter toggles │
|
||||
│ • Distance display on hover │
|
||||
│ • Responsive map container │
|
||||
│ • Theme-aware styling │
|
||||
│ • Click handlers for POI selection │
|
||||
└─────────────────────────────────────────────┘
|
||||
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ CHART COMPONENTS STACK │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Recharts Library
|
||||
│
|
||||
├─ PriceAreaChart (price-area-chart.tsx)
|
||||
│ ├─ Input: PriceAreaChartPoint[] (period, avgPriceM2)
|
||||
│ ├─ Renders: AreaChart with:
|
||||
│ │ • Gradient fill (green/red based on trend)
|
||||
│ │ • Responsive container
|
||||
│ │ • XAxis: period labels
|
||||
│ │ • YAxis: formatted Vietnamese currency (tr/k)
|
||||
│ │ • Tooltip: formatted prices
|
||||
│ └─ Used in: Listing detail page
|
||||
│
|
||||
├─ DistrictHeatmap (district-heatmap.tsx - 9 KB)
|
||||
│ ├─ Input: HeatmapDataPoint[] (district, avgPrice, totalListings)
|
||||
│ ├─ Possibly uses Mapbox layers for geo-visualization
|
||||
│ └─ Used in: Analytics page
|
||||
│
|
||||
├─ PriceTrendChart (price-trend-chart.tsx)
|
||||
│ ├─ Multiple period comparison
|
||||
│ └─ Used in: Analytics, comparative analysis
|
||||
│
|
||||
├─ DistrictBarChart (district-bar-chart.tsx)
|
||||
│ ├─ Bar chart for district-level metrics
|
||||
│ └─ Used in: Market comparisons
|
||||
│
|
||||
├─ AgentPerformance (agent-performance.tsx - 6 KB)
|
||||
│ ├─ Agent metrics visualization
|
||||
│ └─ Used in: Agent detail pages
|
||||
│
|
||||
└─ NeighborhoodRadarChart (neighborhood-radar-chart.tsx)
|
||||
├─ Radar polygon for 6 categories (0-10 scores)
|
||||
└─ Used in: Listing detail, neighborhood info
|
||||
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ COMPONENT IMPORT PATTERNS │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
STATIC IMPORT (always loaded):
|
||||
──────────────────────────────
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
Used for: Base UI components, utilities, small components
|
||||
Bundle impact: Included in main bundle
|
||||
|
||||
|
||||
DYNAMIC IMPORT (lazy-loaded):
|
||||
──────────────────────────────
|
||||
const NeighborhoodRadarChart = dynamic(
|
||||
() => import('@/components/neighborhood')
|
||||
.then(m => m.NeighborhoodRadarChart),
|
||||
{
|
||||
ssr: false, // Don't server-render (client-only due to Recharts)
|
||||
loading: () => <ChartSkeleton /> // Loading UI
|
||||
}
|
||||
);
|
||||
|
||||
Used for: Heavy components (maps, charts, 3D)
|
||||
Bundle impact: Code-split into separate chunk, loaded on-demand
|
||||
|
||||
|
||||
BARREL EXPORT (for organization):
|
||||
─────────────────────────────────
|
||||
// components/neighborhood/index.ts
|
||||
export { NeighborhoodRadarChart } from './neighborhood-radar-chart';
|
||||
export { NeighborhoodPOIMap } from './neighborhood-poi-map';
|
||||
|
||||
// Usage:
|
||||
import { NeighborhoodRadarChart, NeighborhoodPOIMap } from '@/components/neighborhood';
|
||||
|
||||
Benefits: Cleaner imports, single export point
|
||||
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ MAPBOX GL INITIALIZATION FLOW │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
'use client' ← Must be client component (Mapbox GL requires DOM)
|
||||
│
|
||||
├─ import mapboxgl from 'mapbox-gl'
|
||||
├─ import 'mapbox-gl/dist/mapbox-gl.css' ← CSS styles
|
||||
│
|
||||
├─ const mapStyle = useMapboxStyle() ← Get current theme
|
||||
│ └─ Returns: MAPBOX_STYLE_LIGHT or MAPBOX_STYLE_DARK
|
||||
│
|
||||
├─ new mapboxgl.Map({
|
||||
│ container: mapContainerRef.current,
|
||||
│ style: mapStyle,
|
||||
│ center: [lng, lat],
|
||||
│ zoom: 14
|
||||
│ })
|
||||
│
|
||||
├─ On theme change:
|
||||
│ └─ map.setStyle(newStyle) ← Update map style reactively
|
||||
│
|
||||
└─ Cleanup:
|
||||
└─ map?.remove() ← On unmount
|
||||
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ INTERNATIONALIZATION (i18n) FLOW │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Request: /vi/listings/123
|
||||
│
|
||||
├─ [locale] route parameter = "vi"
|
||||
├─ Loaded from: messages/vi.json
|
||||
│
|
||||
└─ Component:
|
||||
const t = useTranslations();
|
||||
<span>{t('listings.detail.title')}</span>
|
||||
│
|
||||
└─ Output: Vietnamese text from translation file
|
||||
|
||||
|
||||
Request: /en/listings/123
|
||||
│
|
||||
├─ [locale] route parameter = "en"
|
||||
├─ Loaded from: messages/en.json
|
||||
│
|
||||
└─ Output: English text from translation file
|
||||
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ SEO & METADATA GENERATION FLOW │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Server Component:
|
||||
generateMetadata({ params }) {
|
||||
│
|
||||
├─ await fetchListingById(params.id) ← Get listing data
|
||||
├─ Construct title, description
|
||||
├─ Get first image for OG
|
||||
│
|
||||
└─ Return Metadata object:
|
||||
{
|
||||
title: "Property Title - Price",
|
||||
description: "Type | Area | Bedrooms | Address | Price",
|
||||
openGraph: {
|
||||
url: "https://goodgo.vn/vi/listings/123",
|
||||
type: "article",
|
||||
images: [{ url: "...", width: 1200, height: 630 }],
|
||||
},
|
||||
alternates: {
|
||||
canonical: "...",
|
||||
languages: { vi: "...", en: "..." }
|
||||
}
|
||||
}
|
||||
|
||||
Output: <meta> tags in <head>
|
||||
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
END OF ARCHITECTURE
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
468
docs/explorations/from-desktop/FRONTEND_EXPLORATION_REPORT.md
Normal file
468
docs/explorations/from-desktop/FRONTEND_EXPLORATION_REPORT.md
Normal file
@@ -0,0 +1,468 @@
|
||||
# GoodGo Platform AI - Next.js Frontend Exploration Report
|
||||
|
||||
## 1. Overall Directory Structure
|
||||
|
||||
### Root Layout
|
||||
```
|
||||
/Users/velikho/Desktop/WORKING/goodgo-platform-ai/apps/web/
|
||||
├── app/ # Next.js App Router directory
|
||||
├── components/ # Reusable React components
|
||||
├── lib/ # API clients, hooks, utilities, stores
|
||||
├── i18n/ # Internationalization config
|
||||
├── messages/ # Translation files
|
||||
├── public/ # Static assets
|
||||
├── node_modules/ # Dependencies
|
||||
└── package.json # Project metadata & dependencies
|
||||
```
|
||||
|
||||
### Key Dependencies (from package.json)
|
||||
- **Framework**: Next.js 15.5.14, React 18.3.0
|
||||
- **State Management**: Zustand 5.0.12 (lightweight state), @tanstack/react-query 5.96.2 (server state)
|
||||
- **Forms**: react-hook-form 7.72.1, @hookform/resolvers 5.2.2, zod 4.3.6
|
||||
- **Mapping**: mapbox-gl 3.21.0, @types/mapbox-gl 3.5.0
|
||||
- **Charts**: recharts 3.8.1
|
||||
- **UI**: Tailwind CSS 3.4.0, class-variance-authority 0.7.1
|
||||
- **Icons**: lucide-react 1.7.0
|
||||
- **Other**: next-intl 4.9.0, next-themes 0.4.6, socket.io-client 4.8.3, html2canvas + jspdf
|
||||
|
||||
---
|
||||
|
||||
## 2. App Router Structure (Next.js 15 App Router)
|
||||
|
||||
### Directory: `/app/[locale]/`
|
||||
Routes are organized by locale (vi/en) with multiple layout groups:
|
||||
|
||||
```
|
||||
app/[locale]/
|
||||
├── (public)/ # Public routes (no auth required)
|
||||
│ ├── listings/
|
||||
│ │ ├── page.tsx # Listings search/browse page
|
||||
│ │ └── [id]/page.tsx # Individual listing detail page
|
||||
│ ├── search/ # Advanced search interface
|
||||
│ ├── khu-cong-nghiep/ # Industrial parks (Vietnamese)
|
||||
│ ├── du-an/ # Projects
|
||||
│ ├── bao-cao/ # Reports
|
||||
│ ├── agents/[id]/ # Agent profiles
|
||||
│ └── payment/return/ # Payment callbacks
|
||||
├── (auth)/ # Authentication routes
|
||||
│ ├── login/page.tsx
|
||||
│ ├── register/page.tsx
|
||||
│ └── auth/callback/ # OAuth callbacks (Google, Zalo)
|
||||
├── (dashboard)/ # Protected routes (auth required)
|
||||
│ ├── dashboard/ # Main dashboard
|
||||
│ ├── analytics/ # Market analytics
|
||||
│ ├── industrial-parks/ # Industrial park management
|
||||
│ ├── valuation/ # Valuation service
|
||||
│ ├── reports/ # User's reports
|
||||
│ └── ...
|
||||
├── (admin)/ # Admin routes
|
||||
│ └── admin/
|
||||
│ ├── users/
|
||||
│ ├── kyc/
|
||||
│ ├── moderation/
|
||||
│ └── ...
|
||||
├── auth/callback/ # Special auth callbacks
|
||||
│ ├── zalo/page.tsx
|
||||
│ └── google/page.tsx
|
||||
└── [locale]/layout.tsx # Root layout wrapping all routes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Components Directory Structure
|
||||
|
||||
### Organized by Domain:
|
||||
```
|
||||
components/
|
||||
├── listings/ # Listing-related components
|
||||
│ ├── listing-detail-client.tsx (39KB - main detail component)
|
||||
│ ├── listing-form-steps.tsx (14KB - form wizard)
|
||||
│ ├── ai-advice-cards.tsx (9KB - AI recommendations)
|
||||
│ ├── image-gallery.tsx
|
||||
│ ├── image-lightbox.tsx
|
||||
│ ├── image-upload.tsx
|
||||
│ ├── inquiry-modal.tsx
|
||||
│ ├── price-history-chart.tsx (uses recharts)
|
||||
│ ├── social-share.tsx
|
||||
│ └── sparkline.tsx
|
||||
│
|
||||
├── neighborhood/ # Neighborhood analysis components
|
||||
│ ├── neighborhood-poi-map.tsx (11KB - POI map with Mapbox)
|
||||
│ ├── neighborhood-radar-chart.tsx (2KB - radar chart)
|
||||
│ ├── neighborhood-score.tsx (2KB - score display)
|
||||
│ ├── types.ts # Shared types & config
|
||||
│ └── index.ts # Barrel export
|
||||
│
|
||||
├── map/ # Map components
|
||||
│ ├── listing-map.tsx (10KB - listings on Mapbox)
|
||||
│ └── location-picker.tsx (9KB - location selection)
|
||||
│
|
||||
├── charts/ # Recharts-based charts
|
||||
│ ├── price-area-chart.tsx (2KB - trend visualization)
|
||||
│ ├── price-trend-chart.tsx
|
||||
│ ├── district-bar-chart.tsx
|
||||
│ ├── district-heatmap.tsx (9KB - with Mapbox)
|
||||
│ └── agent-performance.tsx
|
||||
│
|
||||
├── design-system/ # Reusable UI primitives & patterns
|
||||
│ ├── badge.tsx
|
||||
│ ├── kpi-card.tsx # Key performance indicator card
|
||||
│ ├── stat-card.tsx # Statistics card
|
||||
│ ├── ticker-strip.tsx # Horizontal ticker
|
||||
│ ├── price-delta.tsx # Price change indicator
|
||||
│ ├── signal.tsx # Up/down signal display
|
||||
│ ├── numeric.tsx # Formatted number display
|
||||
│ ├── status-chip.tsx
|
||||
│ ├── empty-state.tsx
|
||||
│ ├── skeleton.tsx
|
||||
│ ├── data-table.tsx # @tanstack/react-table wrapper
|
||||
│ ├── dashboard-layout.tsx
|
||||
│ ├── density-provider.tsx # Density/compact mode
|
||||
│ └── index.ts
|
||||
│
|
||||
├── khu-cong-nghiep/ # Industrial park domain
|
||||
├── du-an/ # Project domain
|
||||
├── agents/ # Real estate agents
|
||||
├── valuation/ # Valuation/AVM features
|
||||
├── search/ # Search interface
|
||||
├── comparison/ # Property comparison
|
||||
├── leads/ # Lead management
|
||||
├── inquiries/ # Inquiry management
|
||||
├── reports/ # Report generation
|
||||
├── chuyen-nhuong/ # Transfer/ownership change
|
||||
├── notifications/
|
||||
├── auth/
|
||||
├── providers/ # React context providers
|
||||
│ ├── auth-provider.tsx
|
||||
│ ├── query-provider.tsx # React Query with error boundary
|
||||
│ ├── theme-provider.tsx # next-themes integration
|
||||
│ ├── notifications-provider.tsx
|
||||
│ └── web-vitals.tsx
|
||||
├── seo/ # SEO components
|
||||
│ └── json-ld.tsx # JSON-LD schema generation
|
||||
├── subscription/
|
||||
├── ui/ # Base UI components (from shadcn/ui)
|
||||
│ ├── badge.tsx
|
||||
│ ├── button.tsx
|
||||
│ ├── card.tsx
|
||||
│ ├── dialog.tsx
|
||||
│ └── ... (standard shadcn components)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Existing Neighborhood Components
|
||||
|
||||
### Key Files:
|
||||
- **`components/neighborhood/types.ts`**
|
||||
- `NeighborhoodCategory`: score category with label, score (0-10), icon
|
||||
- `POIItem`: Point of Interest (school, hospital, etc.) with lat/lng
|
||||
- `NeighborhoodScoreData`: aggregated neighborhood data
|
||||
- `POI_CATEGORY_CONFIG`: config for 6 categories (school, hospital, transit, shopping, restaurant, park)
|
||||
- `DEFAULT_CATEGORIES`: default scoring categories
|
||||
|
||||
- **`components/neighborhood/neighborhood-poi-map.tsx`**
|
||||
- Uses Mapbox GL with hardcoded SVG icons for 6 POI categories
|
||||
- Category filter toggles
|
||||
- Responsive map with custom markers
|
||||
- Integrated POI display
|
||||
|
||||
- **`components/neighborhood/neighborhood-radar-chart.tsx`**
|
||||
- Recharts `RadarChart` component
|
||||
- 0-10 score scale
|
||||
- Badge display below chart
|
||||
- Responsive, themed
|
||||
|
||||
- **`components/neighborhood/neighborhood-score.tsx`**
|
||||
- Small score display component
|
||||
- Used as quick reference
|
||||
|
||||
### Integration in Listing Detail:
|
||||
- Dynamically imported in `listing-detail-client.tsx`
|
||||
- Used when neighborhood data is available from API
|
||||
- Part of the enrichment data pipeline
|
||||
|
||||
---
|
||||
|
||||
## 5. Mapbox GL Integration
|
||||
|
||||
### Setup & Styling:
|
||||
- **`lib/mapbox-style.ts`**
|
||||
```typescript
|
||||
export const MAPBOX_STYLE_LIGHT = 'mapbox://styles/mapbox/streets-v12';
|
||||
export const MAPBOX_STYLE_DARK = 'mapbox://styles/mapbox/dark-v11';
|
||||
|
||||
export function useMapboxStyle(): string {
|
||||
const { theme } = useTheme();
|
||||
return theme === 'dark' ? MAPBOX_STYLE_DARK : MAPBOX_STYLE_LIGHT;
|
||||
}
|
||||
```
|
||||
|
||||
### Map Components:
|
||||
1. **`components/map/listing-map.tsx`** (10KB)
|
||||
- Displays multiple listings as markers
|
||||
- Custom markers with price popup
|
||||
- Click handler for marker selection
|
||||
- Responsive sizing
|
||||
- City coordinate presets for auto-centering
|
||||
|
||||
2. **`components/map/location-picker.tsx`** (9KB)
|
||||
- Interactive location selection
|
||||
- User-clickable map for address input
|
||||
- Reverse geocoding integration
|
||||
|
||||
3. **`components/neighborhood/neighborhood-poi-map.tsx`** (11KB)
|
||||
- POI visualization with category filters
|
||||
- SVG marker icons (6 types)
|
||||
- Distance display
|
||||
- Category toggle buttons
|
||||
|
||||
### Pattern:
|
||||
- Maps created as client components (`'use client'`)
|
||||
- Mapbox GL CSS imported per file
|
||||
- Theme-aware style selection via `useMapboxStyle()`
|
||||
- Responsive containers
|
||||
|
||||
---
|
||||
|
||||
## 6. API Client Setup
|
||||
|
||||
### Main Client: `lib/api-client.ts` (127 lines)
|
||||
```typescript
|
||||
// Core features:
|
||||
- Base URL from environment: process.env.NEXT_PUBLIC_API_URL
|
||||
- CSRF token handling from cookies
|
||||
- Automatic 401 refresh-and-retry (except auth endpoints)
|
||||
- Concurrent refresh coalescing
|
||||
- Type-safe request/response with generics
|
||||
- Credentials: include for cookies
|
||||
|
||||
// API methods:
|
||||
apiClient.get<T>(endpoint, headers?)
|
||||
apiClient.post<T>(endpoint, body?, headers?)
|
||||
apiClient.patch<T>(endpoint, body?, headers?)
|
||||
apiClient.delete<T>(endpoint, headers?)
|
||||
```
|
||||
|
||||
### Domain-Specific API Clients (in `lib/`):
|
||||
- `listings-api.ts` - Listing CRUD, search, recommendations
|
||||
- `analytics-api.ts` - Market reports, heatmaps, price trends
|
||||
- `agents-api.ts` - Agent profiles & stats
|
||||
- `valuation-api.ts` - AVM (Automated Valuation Model) estimates
|
||||
- `khu-cong-nghiep-api.ts` - Industrial park data
|
||||
- `du-an-api.ts` - Project data
|
||||
- `inquiries-api.ts` - Inquiry management
|
||||
- `leads-api.ts` - Lead data
|
||||
- `reports-api.ts` - Report generation
|
||||
- `admin-api.ts` - Admin operations
|
||||
- `auth-api.ts` - Auth endpoints
|
||||
- `payment-api.ts` - Payment processing
|
||||
|
||||
Each API module:
|
||||
- Defines request/response interfaces
|
||||
- Uses `apiClient` helper for HTTP calls
|
||||
- May have both client and server implementations
|
||||
|
||||
---
|
||||
|
||||
## 7. Hooks & React Query Integration
|
||||
|
||||
### React Query Setup: `lib/query-client.ts`
|
||||
```typescript
|
||||
// Creates QueryClient singleton
|
||||
// Provider in components/providers/query-provider.tsx
|
||||
// Includes QueryErrorResetBoundary error handling
|
||||
```
|
||||
|
||||
### Custom Hooks: `lib/hooks/`
|
||||
```
|
||||
use-analytics.ts - Market reports, heatmaps, trends
|
||||
use-khu-cong-nghiep.ts - Industrial parks
|
||||
use-du-an.ts - Projects
|
||||
use-listings.ts - Listings data
|
||||
use-valuation.ts - AVM valuations
|
||||
use-inquiries.ts - Inquiries
|
||||
use-leads.ts - Leads
|
||||
use-reports.ts - Reports
|
||||
use-saved-searches.ts - Saved search queries
|
||||
use-socket-notifications.ts - Real-time notifications
|
||||
```
|
||||
|
||||
### Hook Pattern (example from `use-analytics.ts`):
|
||||
```typescript
|
||||
export const analyticsKeys = {
|
||||
all: ['analytics'] as const,
|
||||
marketReport: (city, period) => [...],
|
||||
// ...
|
||||
};
|
||||
|
||||
export function useMarketReport(city: string, period: string) {
|
||||
return useQuery({
|
||||
queryKey: analyticsKeys.marketReport(city, period),
|
||||
queryFn: () => analyticsApi.getMarketReport(city, period),
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Chart Components
|
||||
|
||||
### Using Recharts:
|
||||
1. **`components/charts/price-area-chart.tsx`**
|
||||
- AreaChart with gradient fills
|
||||
- Up/down signal coloring
|
||||
- Formatted Y-axis (tr/k notation for Vietnamese)
|
||||
- Responsive container
|
||||
|
||||
2. **`components/charts/district-heatmap.tsx`** (9KB)
|
||||
- District-level visualization
|
||||
- Possibly Mapbox layer integration
|
||||
|
||||
3. **`components/charts/price-trend-chart.tsx`**
|
||||
- Historical price trends
|
||||
- Multi-period comparison
|
||||
|
||||
4. **`components/charts/district-bar-chart.tsx`**
|
||||
- Bar chart for district comparisons
|
||||
|
||||
5. **`components/charts/agent-performance.tsx`** (6KB)
|
||||
- Agent metrics visualization
|
||||
|
||||
6. **`components/listings/price-history-chart.tsx`** (2KB)
|
||||
- Listing-specific price history
|
||||
- Small inline chart
|
||||
|
||||
### Recharts Pattern:
|
||||
- `ResponsiveContainer` for responsive sizing
|
||||
- Custom CSS variable colors (--color-signal-up, --signal-down, etc.)
|
||||
- Tooltip with custom styling
|
||||
- Dark/light theme support via CSS variables
|
||||
|
||||
---
|
||||
|
||||
## 9. Listing Detail Page Structure
|
||||
|
||||
### Route: `app/[locale]/(public)/listings/[id]/page.tsx`
|
||||
- Server component (RSC) for metadata generation
|
||||
- Uses `generateMetadata()` for SEO (Open Graph, canonical URLs)
|
||||
- Fetches listing via `fetchListingById()`
|
||||
- Returns `<ListingDetailClient>` for interactivity
|
||||
|
||||
### Client Component: `components/listings/listing-detail-client.tsx` (39KB)
|
||||
**Core Sections:**
|
||||
1. **Image Gallery** - `<ImageGallery>`
|
||||
2. **KPI Strip** - Trader-style metrics (price, m², DOM, etc.)
|
||||
3. **Core Details** - Address, bedrooms, furnishing, etc.
|
||||
4. **Price History Chart** - `<PriceHistoryChart>`
|
||||
5. **Neighborhood Section** - Dynamically loaded:
|
||||
- `<NeighborhoodRadarChart>` (6 categories, 0-10 scores)
|
||||
- `<NeighborhoodPOIMap>` (POI layer)
|
||||
6. **AI Advice Cards** - `<AiAdviceCards>` with personas
|
||||
7. **Similar Listings** - Recommendations
|
||||
8. **Agent Card** - Agent quality score & info
|
||||
9. **Call-to-Action** - Inquiry modal, comparison, AI estimate
|
||||
|
||||
### Key Data Types:
|
||||
```typescript
|
||||
export interface ListingDetail {
|
||||
id: string;
|
||||
status: ListingStatus;
|
||||
transactionType: 'SALE' | 'RENT';
|
||||
priceVND: string;
|
||||
property: { title, address, bedrooms, areaM2, media[], ... };
|
||||
valuationEstimate?: { value, confidence, ... };
|
||||
neighborhoodScore?: NeighborhoodScoreData;
|
||||
agentQualityScore?: AgentQualityScore;
|
||||
similarListings?: ListingSimilarItem[];
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Dynamic Imports (performance):
|
||||
- `NeighborhoodRadarChart` - SSR disabled
|
||||
- `NeighborhoodPOIMap` - SSR disabled + loading fallback
|
||||
|
||||
---
|
||||
|
||||
## 10. State Management Patterns
|
||||
|
||||
### React Query (Server State):
|
||||
- Used for API data fetching & caching
|
||||
- Query keys follow convention pattern
|
||||
- Error boundaries handle failures
|
||||
- Automatic refetch intervals for live data
|
||||
|
||||
### Zustand Stores (Client State):
|
||||
- Examples: `auth-store.ts`, `comparison-store.ts`, `preferences-store.ts`
|
||||
- Lightweight, minimal boilerplate
|
||||
- Integrated with localStorage where needed
|
||||
|
||||
### Context (Theme, Notifications):
|
||||
- `ThemeProvider` (via next-themes)
|
||||
- `QueryProvider` (React Query)
|
||||
- `NotificationsProvider`
|
||||
- `AuthProvider`
|
||||
|
||||
---
|
||||
|
||||
## 11. Key File Patterns & Conventions
|
||||
|
||||
### Naming:
|
||||
- Pages: `page.tsx` in route directories
|
||||
- Layouts: `layout.tsx`
|
||||
- Components: PascalCase with domain prefix (e.g., `NeighborhoodPOIMap`)
|
||||
- API modules: `*-api.ts` for client-side, `*-server.ts` for server-side
|
||||
- Hooks: `use-*` pattern
|
||||
- Types: inline in files or `types.ts` per domain
|
||||
|
||||
### File Organization:
|
||||
- Domain-based (listings/, neighborhood/, khu-cong-nghiep/)
|
||||
- Shared utilities in `/lib` and `/components/design-system`
|
||||
- Barrel exports with `index.ts` for easy importing
|
||||
|
||||
### Code Structure:
|
||||
- 'use client' at top of client components
|
||||
- TypeScript strict mode
|
||||
- Props interfaces above component functions
|
||||
- Sub-components extracted for readability
|
||||
- Helpers defined near top-level components
|
||||
|
||||
---
|
||||
|
||||
## 12. Performance & SEO
|
||||
|
||||
### SEO:
|
||||
- Server-side metadata generation with `generateMetadata()`
|
||||
- JSON-LD schema generation (`components/seo/json-ld.tsx`)
|
||||
- Open Graph + canonical URLs
|
||||
- Multi-language support (vi/en)
|
||||
|
||||
### Performance:
|
||||
- Dynamic imports with code splitting (e.g., maps, heavy charts)
|
||||
- Lazy-loading fallbacks
|
||||
- Image optimization (Next.js Image component)
|
||||
- CSS-in-JS via Tailwind + CVA
|
||||
- Responsive containers for charts/maps
|
||||
|
||||
### Internationalization:
|
||||
- `next-intl` for translations
|
||||
- Route segments by locale: `[locale]/`
|
||||
- Translations in `/messages` directory
|
||||
|
||||
---
|
||||
|
||||
## Summary Table
|
||||
|
||||
| Aspect | Technology | Key Files |
|
||||
|--------|-----------|-----------|
|
||||
| **Mapping** | Mapbox GL 3.21.0 | `components/map/`, `lib/mapbox-style.ts` |
|
||||
| **Charts** | Recharts 3.8.1 | `components/charts/`, `components/neighborhood/neighborhood-radar-chart.tsx` |
|
||||
| **API Client** | Fetch + CSRF | `lib/api-client.ts`, `lib/*-api.ts` |
|
||||
| **State (Server)** | React Query 5.96.2 | `lib/query-client.ts`, `lib/hooks/` |
|
||||
| **State (Client)** | Zustand 5.0.12 | `lib/*-store.ts` |
|
||||
| **Forms** | react-hook-form + Zod | `lib/validations/` |
|
||||
| **UI** | Tailwind + CVA | `components/design-system/`, `components/ui/` |
|
||||
| **Routing** | Next.js App Router | `app/[locale]/` |
|
||||
| **i18n** | next-intl | `/messages/`, route `[locale]/` |
|
||||
|
||||
357
docs/explorations/from-desktop/FRONTEND_QUICK_REFERENCE.txt
Normal file
357
docs/explorations/from-desktop/FRONTEND_QUICK_REFERENCE.txt
Normal file
@@ -0,0 +1,357 @@
|
||||
╔════════════════════════════════════════════════════════════════════════════╗
|
||||
║ GOODGO PLATFORM AI - NEXT.JS FRONTEND QUICK REFERENCE ║
|
||||
╚════════════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
1. PROJECT STRUCTURE
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
/apps/web/
|
||||
├─ app/[locale]/ ← Next.js 15 App Router (public, auth, dashboard, admin)
|
||||
├─ components/ ← React components (listings, neighborhood, map, charts, design-system)
|
||||
├─ lib/ ← API clients, hooks, stores, utilities
|
||||
├─ i18n/ & messages/ ← Internationalization (vi/en)
|
||||
└─ public/ ← Static assets
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
2. CORE TECHNOLOGIES
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Framework: Next.js 15.5.14 (App Router)
|
||||
Runtime: React 18.3.0
|
||||
Styling: Tailwind CSS 3.4.0 + CVA 0.7.1
|
||||
Forms: react-hook-form 7.72.1 + Zod 4.3.6
|
||||
State: Zustand 5.0.12 (client) + React Query 5.96.2 (server)
|
||||
Mapping: Mapbox GL 3.21.0
|
||||
Charts: Recharts 3.8.1
|
||||
Icons: lucide-react 1.7.0
|
||||
i18n: next-intl 4.9.0
|
||||
Theme: next-themes 0.4.6
|
||||
Real-time: socket.io-client 4.8.3
|
||||
PDF Export: html2canvas 1.4.1 + jspdf 4.2.1
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
3. NEIGHBORHOOD FEATURES
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
COMPONENTS:
|
||||
✓ NeighborhoodPOIMap (11 KB, Mapbox GL + 6 POI categories)
|
||||
✓ NeighborhoodRadarChart (2 KB, Recharts radar, 0-10 scores, 6 categories)
|
||||
✓ NeighborhoodScore (2 KB, compact score display)
|
||||
|
||||
TYPES & CONFIG:
|
||||
✓ NeighborhoodCategory (label, score 0-10, icon)
|
||||
✓ POIItem (id, name, category, lat/lng, distance)
|
||||
✓ POICategory (school, hospital, transit, shopping, restaurant, park)
|
||||
✓ NeighborhoodScoreData (overall score, categories, POIs, center)
|
||||
|
||||
INTEGRATION:
|
||||
✓ Dynamically loaded in listing-detail-client.tsx
|
||||
✓ Part of enrichment data pipeline (API response)
|
||||
✓ Maps imported with SSR disabled for performance
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
4. MAPBOX GL USAGE
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
COMPONENTS:
|
||||
• listing-map.tsx Multiple listings + click handlers
|
||||
• location-picker.tsx Interactive location selection
|
||||
• neighborhood-poi-map.tsx POI visualization with filters
|
||||
|
||||
STYLING:
|
||||
• Light: mapbox://styles/mapbox/streets-v12
|
||||
• Dark: mapbox://styles/mapbox/dark-v11
|
||||
• Theme-aware via useMapboxStyle() hook
|
||||
|
||||
PATTERN:
|
||||
'use client' ← Must be client component
|
||||
import 'mapbox-gl/dist/mapbox-gl.css'
|
||||
const style = useMapboxStyle() ← Get theme-aware style
|
||||
new mapboxgl.Map({ style }) ← Create map instance
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
5. CHART COMPONENTS (RECHARTS)
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
✓ price-area-chart.tsx Trend visualization (up/down coloring)
|
||||
✓ price-trend-chart.tsx Historical trends
|
||||
✓ district-bar-chart.tsx District comparisons
|
||||
✓ district-heatmap.tsx Heat visualization (9 KB)
|
||||
✓ agent-performance.tsx Agent metrics (6 KB)
|
||||
✓ neighborhood-radar-chart.tsx Radar chart (0-10 scores)
|
||||
|
||||
PATTERN:
|
||||
ResponsiveContainer ← Responsive sizing
|
||||
CSS variables for theming ← --color-signal-up, --color-signal-down
|
||||
Custom Tooltip styling ← Themed backgrounds
|
||||
Vietnamese number formatting ← tr/k notation
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
6. API CLIENT PATTERN
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
BASE CLIENT (lib/api-client.ts):
|
||||
apiClient.get<T>(endpoint, headers?)
|
||||
apiClient.post<T>(endpoint, body?, headers?)
|
||||
apiClient.patch<T>(endpoint, body?, headers?)
|
||||
apiClient.delete<T>(endpoint, headers?)
|
||||
|
||||
FEATURES:
|
||||
✓ CSRF token handling (from cookies)
|
||||
✓ Automatic 401 refresh-and-retry (except auth endpoints)
|
||||
✓ Concurrent refresh coalescing
|
||||
✓ Type-safe with generics
|
||||
✓ Base URL from NEXT_PUBLIC_API_URL env var
|
||||
|
||||
DOMAIN-SPECIFIC CLIENTS (lib/*-api.ts):
|
||||
listings-api.ts | Listing CRUD, search
|
||||
analytics-api.ts | Market reports, heatmaps, trends
|
||||
neighborhood-api.ts | Neighborhood scoring (if exists)
|
||||
valuation-api.ts | AVM estimates
|
||||
agents-api.ts | Agent profiles
|
||||
khu-cong-nghiep-api | Industrial parks
|
||||
du-an-api.ts | Projects
|
||||
inquiries-api.ts | Inquiries
|
||||
leads-api.ts | Leads
|
||||
admin-api.ts | Admin operations
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
7. REACT QUERY HOOKS (lib/hooks/)
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
PATTERN:
|
||||
const analyticsKeys = {
|
||||
all: ['analytics'] as const,
|
||||
marketReport: (city, period) => ['analytics', 'market-report', city, period] as const,
|
||||
};
|
||||
|
||||
export function useMarketReport(city: string, period: string) {
|
||||
return useQuery({
|
||||
queryKey: analyticsKeys.marketReport(city, period),
|
||||
queryFn: () => analyticsApi.getMarketReport(city, period),
|
||||
});
|
||||
}
|
||||
|
||||
AVAILABLE HOOKS:
|
||||
use-analytics.ts useMarketReport, useHeatmap, usePriceTrend, etc.
|
||||
use-listings.ts useListings, useListing
|
||||
use-khu-cong-nghiep.ts useIndustrialParks
|
||||
use-du-an.ts useProjects
|
||||
use-valuation.ts useValuation
|
||||
use-inquiries.ts useInquiries
|
||||
use-leads.ts useLeads
|
||||
use-reports.ts useReports
|
||||
use-saved-searches.ts useSavedSearches
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
8. LISTING DETAIL PAGE FLOW
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
ROUTE: /[locale]/(public)/listings/[id]/page.tsx
|
||||
|
||||
1. SERVER COMPONENT (RSC)
|
||||
├─ generateMetadata() ← SEO: OG, canonical, schema
|
||||
└─ fetchListingById(id) ← Data fetching
|
||||
|
||||
2. CLIENT COMPONENT (listing-detail-client.tsx - 39 KB)
|
||||
├─ ImageGallery ← Media slideshow
|
||||
├─ KPI Strip ← Trader-style metrics
|
||||
├─ Core Details ← Address, bedrooms, etc.
|
||||
├─ PriceHistoryChart ← Recharts trend
|
||||
├─ Neighborhood Section
|
||||
│ ├─ NeighborhoodRadarChart ← (dynamic import, SSR: false)
|
||||
│ └─ NeighborhoodPOIMap ← (dynamic import, SSR: false)
|
||||
├─ AiAdviceCards ← Personas
|
||||
├─ SimilarListings ← Recommendations
|
||||
├─ AgentCard ← Quality score
|
||||
└─ CTA Section ← Inquiry, comparison, estimate
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
9. STATE MANAGEMENT LAYERS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
LAYER 1: REACT QUERY (Server State)
|
||||
Purpose: Fetch & cache API data
|
||||
Provider: QueryProvider (components/providers/query-provider.tsx)
|
||||
Error Handle: QueryErrorResetBoundary + custom error boundary
|
||||
Usage: useQuery, useMutation hooks
|
||||
|
||||
LAYER 2: ZUSTAND (Client State)
|
||||
Purpose: UI state, preferences
|
||||
Examples: auth-store, comparison-store, preferences-store
|
||||
Persistence: localStorage integration
|
||||
Usage: Direct store access or hooks
|
||||
|
||||
LAYER 3: CONTEXT (Global Features)
|
||||
Theme: ThemeProvider (next-themes)
|
||||
Notifications: NotificationsProvider
|
||||
Auth: AuthProvider
|
||||
Query: QueryProvider
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
10. DESIGN SYSTEM COMPONENTS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
FINANCIAL METRICS:
|
||||
✓ kpi-card.tsx Key performance indicator
|
||||
✓ stat-card.tsx Statistics display
|
||||
✓ ticker-strip.tsx Horizontal ticker/marquee
|
||||
✓ price-delta.tsx Price change with signal
|
||||
✓ signal.tsx Up/down indicator
|
||||
✓ numeric.tsx Formatted number display
|
||||
|
||||
DATA DISPLAY:
|
||||
✓ data-table.tsx @tanstack/react-table wrapper
|
||||
✓ empty-state.tsx Empty data fallback
|
||||
✓ skeleton.tsx Loading skeleton
|
||||
✓ status-chip.tsx Status badge
|
||||
|
||||
LAYOUT:
|
||||
✓ dashboard-layout.tsx Dashboard structure
|
||||
✓ density-provider.tsx Compact/dense mode toggle
|
||||
✓ divider.tsx Visual separator
|
||||
|
||||
UI PRIMITIVES (from shadcn/ui via design-system/):
|
||||
✓ badge.tsx, button.tsx, card.tsx, dialog.tsx, etc.
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
11. INTERNATIONALIZATION (i18n)
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
SETUP:
|
||||
Library: next-intl 4.9.0
|
||||
Locales: vi (Vietnamese), en (English)
|
||||
Route param: [locale] prefix
|
||||
Translations: /messages/ directory
|
||||
|
||||
USAGE:
|
||||
const t = useTranslations();
|
||||
<span>{t('key.path')}</span>
|
||||
|
||||
ROUTES:
|
||||
/vi/listings/[id] ← Vietnamese
|
||||
/en/listings/[id] ← English
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
12. PERFORMANCE OPTIMIZATIONS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
CODE SPLITTING:
|
||||
dynamic(() => import(...), { ssr: false }) ← Heavy components (maps, charts)
|
||||
|
||||
LAZY LOADING:
|
||||
loading: () => <Fallback /> ← Show UI while loading
|
||||
|
||||
IMAGE OPTIMIZATION:
|
||||
next/image ← Built-in optimization
|
||||
|
||||
CACHING:
|
||||
React Query query keys ← Automatic deduplication & refetch intervals
|
||||
|
||||
CSS-IN-JS:
|
||||
Tailwind + CVA ← Static + composable variants
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
13. KEY FILE PATHS REFERENCE
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
NEIGHBORHOOD:
|
||||
lib/components/neighborhood/ Components
|
||||
lib/components/neighborhood/types.ts Type definitions
|
||||
lib/components/neighborhood/neighborhood-poi-map.tsx Mapbox POI layer
|
||||
lib/components/neighborhood/neighborhood-radar-chart.tsx Radar scores
|
||||
|
||||
MAPPING:
|
||||
lib/mapbox-style.ts Theme-aware styles
|
||||
lib/components/map/listing-map.tsx Listings on map
|
||||
lib/components/map/location-picker.tsx Location selection
|
||||
|
||||
CHARTS:
|
||||
lib/components/charts/ Chart components
|
||||
lib/components/charts/price-area-chart.tsx Area trend chart
|
||||
|
||||
API & STATE:
|
||||
lib/api-client.ts Base HTTP client
|
||||
lib/listings-api.ts Listings endpoints
|
||||
lib/analytics-api.ts Analytics endpoints
|
||||
lib/hooks/use-analytics.ts Analytics React Query hooks
|
||||
|
||||
LISTING DETAIL:
|
||||
app/[locale]/(public)/listings/[id]/page.tsx Server component
|
||||
lib/components/listings/listing-detail-client.tsx Client component (39 KB)
|
||||
|
||||
PROVIDERS:
|
||||
lib/components/providers/query-provider.tsx React Query setup
|
||||
lib/components/providers/theme-provider.tsx Theme management
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
14. CODING PATTERNS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
COMPONENT FILE HEADER:
|
||||
'use client' ← Client component marker
|
||||
import ... ← External imports
|
||||
import ... ← Internal imports
|
||||
|
||||
interface ComponentProps { ... } ← Props interface
|
||||
|
||||
export function Component({ ... }: ComponentProps) {
|
||||
// implementation
|
||||
}
|
||||
|
||||
API CLIENT USAGE:
|
||||
const data = await apiClient.get<Type>('/endpoint');
|
||||
const result = await apiClient.post<Type>('/endpoint', body);
|
||||
|
||||
REACT QUERY HOOK:
|
||||
export function useData(param: string) {
|
||||
return useQuery({
|
||||
queryKey: ['key', param],
|
||||
queryFn: () => api.getData(param),
|
||||
enabled: !!param, ← Conditional queries
|
||||
});
|
||||
}
|
||||
|
||||
COMPONENT IMPORTS:
|
||||
import dynamic from 'next/dynamic';
|
||||
const Map = dynamic(() => import('...').then(m => m.Component), {
|
||||
ssr: false,
|
||||
loading: () => <LoadingState />
|
||||
});
|
||||
|
||||
ZUSTAND STORE:
|
||||
import { create } from 'zustand';
|
||||
const useStore = create((set) => ({
|
||||
value: null,
|
||||
setValue: (v) => set({ value: v }),
|
||||
}));
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
15. DEBUGGING & DEVELOPMENT TIPS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
ENV VARS:
|
||||
NEXT_PUBLIC_API_URL ← Backend API base URL
|
||||
NEXT_PUBLIC_SITE_URL ← Frontend base URL
|
||||
MAPBOX_ACCESS_TOKEN ← Mapbox API key
|
||||
|
||||
SCRIPTS:
|
||||
npm run dev ← Start dev server (port 3000)
|
||||
npm run build ← Production build
|
||||
npm run lint ← ESLint
|
||||
npm test ← Vitest
|
||||
npm run typecheck ← TypeScript check
|
||||
|
||||
HOT TIPS:
|
||||
• Dynamic imports reduce bundle size for heavy components
|
||||
• React Query keys are the foundation of caching strategy
|
||||
• Use CSS variables for theming (see design-system/)
|
||||
• Barrel exports (index.ts) for cleaner imports
|
||||
• Zustand for simple state, React Query for server state
|
||||
• Always check enabled condition in useQuery() for dependent queries
|
||||
|
||||
╔════════════════════════════════════════════════════════════════════════════╗
|
||||
║ Report Generated: April 2026 ║
|
||||
║ Base Path: /Users/velikho/Desktop/WORKING/goodgo-platform-ai/apps/web ║
|
||||
╚════════════════════════════════════════════════════════════════════════════╝
|
||||
241
docs/explorations/from-desktop/INDEX_frontend_exploration.md
Normal file
241
docs/explorations/from-desktop/INDEX_frontend_exploration.md
Normal file
@@ -0,0 +1,241 @@
|
||||
# GoodGo Frontend Exploration - Complete Documentation
|
||||
|
||||
## 📁 Generated Files
|
||||
|
||||
All files are saved to your Desktop. Read them in this order:
|
||||
|
||||
### 1. **README_EXPLORATION.txt** (16 KB) ⭐ START HERE
|
||||
- **Purpose**: Overview & quick summary
|
||||
- **Contents**:
|
||||
- Key findings (10 major insights)
|
||||
- Dependencies summary
|
||||
- Component import patterns
|
||||
- Quick start guide
|
||||
- Notable implementation details
|
||||
- Pro tips & file size reference
|
||||
- Next steps for development
|
||||
|
||||
### 2. **ARCHITECTURE_OVERVIEW.txt** (24 KB)
|
||||
- **Purpose**: Visual diagrams & data flows
|
||||
- **Contents**:
|
||||
- Routing & layout hierarchy (ASCII tree)
|
||||
- Listing detail page component flow
|
||||
- API & state management data flow
|
||||
- Neighborhood components detail
|
||||
- Chart components stack
|
||||
- Component import patterns (static/dynamic)
|
||||
- Mapbox GL initialization flow
|
||||
- i18n flow diagram
|
||||
- SEO & metadata generation flow
|
||||
|
||||
### 3. **FRONTEND_QUICK_REFERENCE.txt** (19 KB)
|
||||
- **Purpose**: Quick lookup reference
|
||||
- **Contents**: 15 sections covering:
|
||||
- Project structure
|
||||
- Core tech stack
|
||||
- Neighborhood features detail
|
||||
- Mapbox GL patterns
|
||||
- Chart components
|
||||
- API client architecture
|
||||
- React Query hooks
|
||||
- Listing detail page flow
|
||||
- State management layers
|
||||
- Design system components
|
||||
- i18n setup
|
||||
- Performance optimizations
|
||||
- Key file paths
|
||||
- Coding patterns
|
||||
- Debugging tips
|
||||
|
||||
### 4. **FRONTEND_EXPLORATION_REPORT.md** (16 KB)
|
||||
- **Purpose**: Deep technical dive
|
||||
- **Contents**: 12 comprehensive sections:
|
||||
1. Overall directory structure
|
||||
2. App Router structure
|
||||
3. Components directory (23 domains)
|
||||
4. Existing neighborhood components
|
||||
5. Mapbox GL integration
|
||||
6. API client setup
|
||||
7. Hooks & React Query integration
|
||||
8. Chart components (Recharts)
|
||||
9. Listing detail page structure
|
||||
10. State management patterns
|
||||
11. Key file patterns & conventions
|
||||
12. Performance & SEO optimizations
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ Quick Navigation
|
||||
|
||||
### For different needs, use:
|
||||
|
||||
**"I want a 5-minute overview"**
|
||||
→ Read: **README_EXPLORATION.txt** (Key Findings section)
|
||||
|
||||
**"I need to understand the architecture"**
|
||||
→ Read: **ARCHITECTURE_OVERVIEW.txt** (start with the diagrams)
|
||||
|
||||
**"I need to find a specific file or pattern"**
|
||||
→ Search: **FRONTEND_QUICK_REFERENCE.txt** (Use Ctrl+F)
|
||||
|
||||
**"I need complete technical details"**
|
||||
→ Read: **FRONTEND_EXPLORATION_REPORT.md** (full reference)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Project Stats
|
||||
|
||||
- **Framework**: Next.js 15.5.14 with App Router
|
||||
- **Components**: 23 domains with 15+ major feature areas
|
||||
- **Mapping**: Mapbox GL 3.21.0 (3 map components)
|
||||
- **Charts**: Recharts 3.8.1 (6 chart types)
|
||||
- **Neighborhood Features**: Fully implemented (3 components)
|
||||
- **API Modules**: 12+ domain-specific clients
|
||||
- **React Query Hooks**: 10+ custom hooks
|
||||
- **Routes**: Public, Auth, Dashboard, Admin (all locale-aware)
|
||||
- **Listing Detail Page**: 39 KB client component with 9 sections
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Key Takeaways
|
||||
|
||||
### Architecture
|
||||
- ✅ Domain-based component organization
|
||||
- ✅ Separated concerns (API, hooks, stores, components)
|
||||
- ✅ Locale-aware routing with i18n
|
||||
- ✅ Dynamic imports for performance
|
||||
- ✅ Type-safe API client with CSRF protection
|
||||
|
||||
### Features
|
||||
- ✅ Neighborhood scoring (6 categories, 0-10 scale)
|
||||
- ✅ POI mapping (6 categories with SVG icons)
|
||||
- ✅ Price history charts
|
||||
- ✅ Theme switching (light/dark) with Mapbox
|
||||
- ✅ Multi-language support (Vietnamese/English)
|
||||
|
||||
### Tech Stack
|
||||
- ✅ React 18 + Next.js 15 App Router
|
||||
- ✅ Tailwind CSS + CVA for styling
|
||||
- ✅ React Query + Zustand for state
|
||||
- ✅ Mapbox GL for mapping
|
||||
- ✅ Recharts for data visualization
|
||||
- ✅ next-intl for internationalization
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Key Files
|
||||
|
||||
```
|
||||
lib/
|
||||
├── api-client.ts # Base HTTP client (CSRF, 401 refresh)
|
||||
├── listings-api.ts # Listing endpoints
|
||||
├── analytics-api.ts # Analytics/market data
|
||||
├── mapbox-style.ts # Theme-aware map styles
|
||||
└── hooks/
|
||||
└── use-analytics.ts # React Query pattern example
|
||||
|
||||
components/
|
||||
├── listings/
|
||||
│ └── listing-detail-client.tsx # Main detail page (39 KB)
|
||||
├── neighborhood/
|
||||
│ ├── neighborhood-poi-map.tsx # Mapbox POI layer (11 KB)
|
||||
│ ├── neighborhood-radar-chart.tsx # Recharts radar (2 KB)
|
||||
│ └── types.ts # Neighborhood types
|
||||
├── map/
|
||||
│ ├── listing-map.tsx # Listings on map
|
||||
│ └── location-picker.tsx # Location selection
|
||||
├── charts/
|
||||
│ ├── price-area-chart.tsx # Area chart
|
||||
│ ├── district-heatmap.tsx # Heat visualization
|
||||
│ └── ...
|
||||
└── design-system/ # UI components & patterns
|
||||
|
||||
app/
|
||||
└── [locale]/
|
||||
├── (public)/listings/[id]/page.tsx # Detail page (Server RSC)
|
||||
├── (dashboard)/ # Protected routes
|
||||
└── (admin)/ # Admin routes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 Important Patterns
|
||||
|
||||
### API Usage
|
||||
```typescript
|
||||
const data = await apiClient.get<Type>('/endpoint');
|
||||
```
|
||||
|
||||
### React Query Hook
|
||||
```typescript
|
||||
export function useData(param: string) {
|
||||
return useQuery({
|
||||
queryKey: ['key', param],
|
||||
queryFn: () => api.getData(param),
|
||||
enabled: !!param,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Dynamic Component Import
|
||||
```typescript
|
||||
const Component = dynamic(() => import('...'), {
|
||||
ssr: false,
|
||||
loading: () => <Skeleton />
|
||||
});
|
||||
```
|
||||
|
||||
### Mapbox Integration
|
||||
```typescript
|
||||
'use client'
|
||||
const style = useMapboxStyle(); // light/dark
|
||||
const map = new mapboxgl.Map({ style });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Commands
|
||||
|
||||
```bash
|
||||
npm run dev # Dev server (port 3000)
|
||||
npm run build # Production build
|
||||
npm run lint # ESLint check
|
||||
npm test # Vitest tests
|
||||
npm run typecheck # TypeScript check
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Notes
|
||||
|
||||
- All reports are **self-contained** - you can read any of them independently
|
||||
- File paths are **absolute** from `/apps/web`
|
||||
- Component sizes range from **2 KB** (radar chart) to **39 KB** (listing detail)
|
||||
- Neighborhood features are **already implemented** and ready to use
|
||||
- Mapbox GL requires **'use client'** directive
|
||||
- All charts use **CSS variables** for theming
|
||||
- i18n uses **[locale]** route parameter (vi/en)
|
||||
|
||||
---
|
||||
|
||||
## 📞 Quick Reference
|
||||
|
||||
**What does NeighborhoodPOIMap do?**
|
||||
→ Section 3 of README, or QUICK_REFERENCE section 3
|
||||
|
||||
**How do I add a new chart?**
|
||||
→ QUICK_REFERENCE section 5 (Chart Components)
|
||||
|
||||
**Where's the API client?**
|
||||
→ QUICK_REFERENCE section 6 (API Client Pattern)
|
||||
|
||||
**How does state management work?**
|
||||
→ README section "State Management Layers" or QUICK_REFERENCE section 9
|
||||
|
||||
**What about performance?**
|
||||
→ QUICK_REFERENCE section 12 (Performance Optimizations)
|
||||
|
||||
---
|
||||
|
||||
Generated: April 21, 2026
|
||||
Base Path: `/Users/velikho/Desktop/WORKING/goodgo-platform-ai/apps/web`
|
||||
287
docs/explorations/from-desktop/NOTIFICATIONS_EXPLORATION.md
Normal file
287
docs/explorations/from-desktop/NOTIFICATIONS_EXPLORATION.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# Notifications Module - Complete Exploration
|
||||
|
||||
## 📊 Summary
|
||||
- **Total Files**: 67 TypeScript files
|
||||
- **Architecture**: DDD + CQRS
|
||||
- **Channels**: EMAIL, SMS, PUSH (FCM), ZALO_OA
|
||||
- **Event Listeners**: 18 domain event listeners
|
||||
- **Current SMS Provider**: Stringee (fully implemented)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Main Interface: NotificationChannelPort
|
||||
|
||||
**Location**: `domain/ports/notification-channel.port.ts`
|
||||
|
||||
```typescript
|
||||
export interface SendChannelMessageDto {
|
||||
recipient: string; // Email/phone/token
|
||||
subject: string; // For EMAIL
|
||||
body: string; // HTML content
|
||||
templateKey: string; // e.g., 'user.registered'
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface SendChannelMessageResult {
|
||||
messageId: string;
|
||||
}
|
||||
|
||||
export interface NotificationChannelPort {
|
||||
readonly channel: NotificationChannel; // 'EMAIL' | 'SMS' | 'PUSH' | 'ZALO_OA'
|
||||
readonly isAvailable: boolean;
|
||||
send(dto: SendChannelMessageDto): Promise<SendChannelMessageResult>;
|
||||
}
|
||||
|
||||
export const SMS_NOTIFICATION_CHANNEL = Symbol('SMS_NOTIFICATION_CHANNEL');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📡 SMS Rate Limiting
|
||||
|
||||
**Location**: `infrastructure/services/sms-rate-limiter.service.ts`
|
||||
|
||||
- **Technology**: Redis + Lua script (atomic, sliding window)
|
||||
- **Buckets**:
|
||||
- `otp`: 5/min, 10/hour (strict)
|
||||
- `transactional`: 20/min, 100/hour (lenient)
|
||||
- **Graceful degradation**: Redis error → allow request
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Stringee SMS Service (Reference Implementation)
|
||||
|
||||
**Location**: `infrastructure/services/stringee-sms.service.ts`
|
||||
|
||||
### Constructor Dependencies
|
||||
- `LoggerService`
|
||||
- `SmsRateLimiterService`
|
||||
|
||||
### Environment Variables
|
||||
- `STRINGEE_API_KEY` (required)
|
||||
- `STRINGEE_BRANDNAME` (optional, default: "GoodGo")
|
||||
|
||||
### Key Methods
|
||||
1. `onModuleInit()` - Read env, validate, initialize
|
||||
2. `get isAvailable()` - Check if initialized
|
||||
3. `async send(dto)` - NotificationChannelPort implementation
|
||||
4. `async sendOTP(dto)` - Specific OTP handling
|
||||
5. `async dispatch(dto)` - Main workflow
|
||||
6. `async enforceRateLimit()` - Check per-minute AND hourly
|
||||
7. `async sendWithRetry()` - 3 attempts with exponential backoff
|
||||
8. `normalizePhone()` - +84xxx format conversion
|
||||
9. `stripHtml()` - Remove HTML tags
|
||||
|
||||
### Workflow
|
||||
1. Rate limit check (per-minute + hourly)
|
||||
2. HTML stripping
|
||||
3. Phone normalization (+84, 84, 0 formats)
|
||||
4. Retry logic (1s → 2s → 4s)
|
||||
5. API call to https://api.stringee.com/v1/sms
|
||||
6. Return messageId
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ DI Configuration
|
||||
|
||||
**Location**: `notifications.module.ts`
|
||||
|
||||
```typescript
|
||||
@Module({
|
||||
providers: [
|
||||
// Repositories
|
||||
{ provide: NOTIFICATION_REPOSITORY, useClass: PrismaNotificationRepository },
|
||||
{ provide: NOTIFICATION_PREFERENCE_REPOSITORY, useClass: PrismaNotificationPreferenceRepository },
|
||||
|
||||
// Services
|
||||
EmailService,
|
||||
FcmService,
|
||||
SmsRateLimiterService,
|
||||
StringeeSmsService,
|
||||
{ provide: SMS_NOTIFICATION_CHANNEL, useExisting: StringeeSmsService },
|
||||
ZaloOaService,
|
||||
TemplateService,
|
||||
|
||||
// Others
|
||||
NotificationsGateway,
|
||||
SendNotificationHandler,
|
||||
...EventListeners,
|
||||
],
|
||||
exports: [
|
||||
EmailService,
|
||||
FcmService,
|
||||
SmsRateLimiterService,
|
||||
StringeeSmsService,
|
||||
SMS_NOTIFICATION_CHANNEL,
|
||||
ZaloOaService,
|
||||
TemplateService,
|
||||
NotificationsGateway,
|
||||
],
|
||||
})
|
||||
export class NotificationsModule {}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Message Flow
|
||||
|
||||
```
|
||||
Domain Event (e.g., user.registered)
|
||||
↓
|
||||
@OnEvent('user.registered') Listener
|
||||
↓
|
||||
CommandBus.execute(SendNotificationCommand)
|
||||
↓
|
||||
SendNotificationHandler
|
||||
├─ Check user preference (enabled?)
|
||||
├─ Render template
|
||||
├─ Persist notification log
|
||||
├─ Dispatch to channel:
|
||||
│ ├─ EMAIL → EmailService.send()
|
||||
│ ├─ SMS → StringeeSmsService.send() [with rate limit + retry]
|
||||
│ ├─ PUSH → FcmService.send()
|
||||
│ └─ ZALO → ZaloOaService.send()
|
||||
├─ Update status (SENT/FAILED)
|
||||
└─ Publish NotificationSentEvent
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 Key Patterns
|
||||
|
||||
### Pattern 1: OnModuleInit + Lazy Initialization
|
||||
- Read env vars in `onModuleInit()`
|
||||
- Set `initialized = true` only if valid
|
||||
- Check `isAvailable` getter before operations
|
||||
|
||||
### Pattern 2: DI Token Binding
|
||||
```typescript
|
||||
export const MY_TOKEN = Symbol('MY_TOKEN');
|
||||
|
||||
providers: [
|
||||
MyService,
|
||||
{ provide: MY_TOKEN, useExisting: MyService }
|
||||
]
|
||||
|
||||
// Inject
|
||||
@Inject(MY_TOKEN) myService: SomeInterface
|
||||
```
|
||||
|
||||
### Pattern 3: Rate Limiting
|
||||
- Always call `rateLimiter.check(phone, bucket)` first
|
||||
- Support OTP vs transactional buckets
|
||||
- Check BOTH per-minute and hourly limits
|
||||
- Throw `DomainException` on violation
|
||||
|
||||
### Pattern 4: Error Handling
|
||||
```typescript
|
||||
throw new DomainException(
|
||||
ErrorCode.TOO_MANY_REQUESTS,
|
||||
`SMS limit exceeded. Retry after ${retryAfterSeconds}s.`,
|
||||
HttpStatus.TOO_MANY_REQUESTS,
|
||||
{ bucket, retryAfterSeconds }
|
||||
);
|
||||
```
|
||||
|
||||
### Pattern 5: Retry Logic
|
||||
- MAX_RETRIES = 3
|
||||
- Exponential backoff: 1s → 2s → 4s
|
||||
- Log each attempt, fail silently on final retry
|
||||
|
||||
---
|
||||
|
||||
## 📁 Directory Structure
|
||||
|
||||
```
|
||||
notifications/
|
||||
├── domain/
|
||||
│ ├── ports/
|
||||
│ │ └── notification-channel.port.ts ⭐ Main interface
|
||||
│ ├── repositories/ ← DI Tokens
|
||||
│ ├── value-objects/
|
||||
│ │ └── notification-channel.vo.ts ← Channel enum
|
||||
│ ├── entities/
|
||||
│ └── events/
|
||||
│ └── notification-sent.event.ts
|
||||
├── infrastructure/
|
||||
│ ├── services/
|
||||
│ │ ├── stringee-sms.service.ts ⭐ SMS provider example
|
||||
│ │ ├── sms-rate-limiter.service.ts ← Rate limiting
|
||||
│ │ ├── email.service.ts
|
||||
│ │ ├── fcm.service.ts
|
||||
│ │ └── zalo-oa.service.ts
|
||||
│ └── repositories/
|
||||
│ ├── prisma-notification.repository.ts
|
||||
│ └── prisma-notification-preference.repository.ts
|
||||
├── application/
|
||||
│ ├── commands/
|
||||
│ │ └── send-notification/
|
||||
│ │ ├── send-notification.command.ts
|
||||
│ │ └── send-notification.handler.ts ⭐ Command dispatcher
|
||||
│ └── listeners/ ← 18 event listeners
|
||||
├── presentation/
|
||||
│ ├── controllers/
|
||||
│ └── gateways/
|
||||
│ └── notifications.gateway.ts ← WebSocket
|
||||
├── notifications.module.ts ⭐ DI setup
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Implementation Checklist (for new SMS provider)
|
||||
|
||||
- [ ] Create `infrastructure/services/{provider}-sms.service.ts`
|
||||
- [ ] Implement `NotificationChannelPort` interface
|
||||
- [ ] Implement `OnModuleInit` for env var reading
|
||||
- [ ] Inject `SmsRateLimiterService`
|
||||
- [ ] Implement rate limit enforcement
|
||||
- [ ] Implement phone normalization
|
||||
- [ ] Strip HTML from body
|
||||
- [ ] Implement retry logic (3 attempts, exponential backoff)
|
||||
- [ ] Register in `notifications.module.ts`
|
||||
- [ ] Integrate with `SendNotificationHandler`
|
||||
- [ ] Write unit tests
|
||||
|
||||
---
|
||||
|
||||
## 📊 SMS Rate Limits
|
||||
|
||||
| Bucket | Per-Minute | Per-Hour | Use Case |
|
||||
|--------|-----------|----------|----------|
|
||||
| `otp` | 5 | 10 | OTP codes, verification |
|
||||
| `transactional` | 20 | 100 | Regular notifications |
|
||||
|
||||
**OTP Templates** (use strict limits):
|
||||
- user.phone_change_otp
|
||||
- auth.login_otp
|
||||
- auth.kyc_otp
|
||||
- auth.phone_verify_otp
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Quick Reference
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `domain/ports/notification-channel.port.ts` | Interface + DI symbols |
|
||||
| `infrastructure/services/stringee-sms.service.ts` | SMS provider example |
|
||||
| `infrastructure/services/sms-rate-limiter.service.ts` | Rate limiting |
|
||||
| `application/commands/send-notification/send-notification.handler.ts` | Command handler |
|
||||
| `notifications.module.ts` | DI configuration |
|
||||
| `application/listeners/user-registered.listener.ts` | Event listener example |
|
||||
|
||||
---
|
||||
|
||||
## ✅ Everything You Need
|
||||
|
||||
- [x] Full directory structure
|
||||
- [x] Main interface (NotificationChannelPort)
|
||||
- [x] Existing SMS implementation (Stringee - complete reference)
|
||||
- [x] Rate limiting system (Redis-based)
|
||||
- [x] DI configuration patterns
|
||||
- [x] Message flow understanding
|
||||
- [x] Handler integration patterns
|
||||
- [x] Error handling patterns
|
||||
- [x] Testing patterns
|
||||
|
||||
319
docs/explorations/from-desktop/README_EXPLORATION.txt
Normal file
319
docs/explorations/from-desktop/README_EXPLORATION.txt
Normal file
@@ -0,0 +1,319 @@
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ FRONTEND EXPLORATION SUMMARY ║
|
||||
║ GoodGo Platform AI - Next.js ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
EXPLORATION COMPLETED: April 21, 2026
|
||||
BASE PATH: /Users/velikho/Desktop/WORKING/goodgo-platform-ai/apps/web
|
||||
|
||||
📋 REPORTS GENERATED:
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
1. FRONTEND_EXPLORATION_REPORT.md (16 KB)
|
||||
└─ Comprehensive 12-section technical deep-dive
|
||||
• Project structure (directory layout)
|
||||
• Core technologies & dependencies
|
||||
• App Router structure & route organization
|
||||
• Component directory (23 domains)
|
||||
• Neighborhood components detail
|
||||
• Mapbox GL integration patterns
|
||||
• API client setup & domain-specific modules
|
||||
• React Query hooks & caching strategy
|
||||
• Chart components (Recharts)
|
||||
• Listing detail page structure (39 KB client component)
|
||||
• State management layers (React Query, Zustand, Context)
|
||||
• Key file patterns & conventions
|
||||
• Performance & SEO optimizations
|
||||
|
||||
2. FRONTEND_QUICK_REFERENCE.txt (19 KB)
|
||||
└─ 15-section quick lookup guide
|
||||
• Project structure overview
|
||||
• Core tech stack
|
||||
• Neighborhood features & types
|
||||
• Mapbox GL usage patterns
|
||||
• Chart components
|
||||
• API client patterns
|
||||
• React Query hooks
|
||||
• Listing detail page flow
|
||||
• State management layers
|
||||
• Design system components
|
||||
• Internationalization setup
|
||||
• Performance optimizations
|
||||
• Key file paths reference
|
||||
• Coding patterns examples
|
||||
• Debugging tips
|
||||
|
||||
3. ARCHITECTURE_OVERVIEW.txt (16 KB)
|
||||
└─ Visual ASCII diagrams & data flows
|
||||
• Routing & layout hierarchy
|
||||
• Listing detail page component flow
|
||||
• API & state management data flow
|
||||
• Neighborhood components detail
|
||||
• Chart components stack
|
||||
• Component import patterns (static/dynamic)
|
||||
• Mapbox GL initialization flow
|
||||
• i18n flow
|
||||
• SEO & metadata generation
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🏗️ KEY FINDINGS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
1. DIRECTORY STRUCTURE
|
||||
✓ Domain-based organization (listings/, neighborhood/, map/, charts/)
|
||||
✓ Shared utilities in /lib (API clients, hooks, stores)
|
||||
✓ Design system components in /components/design-system/
|
||||
✓ Next.js 15 App Router with locale prefix [locale]
|
||||
✓ Multi-level layout hierarchy: root → locale → group → feature
|
||||
|
||||
2. NEIGHBORHOOD FEATURES (FULLY IMPLEMENTED)
|
||||
✓ NeighborhoodPOIMap (11 KB) - Mapbox GL with 6 POI categories
|
||||
✓ NeighborhoodRadarChart (2 KB) - Recharts radar with 0-10 scores
|
||||
✓ NeighborhoodScore (2 KB) - Compact score display
|
||||
✓ Rich type definitions (NeighborhoodScoreData, POIItem, NeighborhoodCategory)
|
||||
✓ Integration point: Dynamically loaded in listing-detail-client.tsx
|
||||
|
||||
3. MAPBOX GL INTEGRATION
|
||||
✓ 3 map components: listing-map, location-picker, neighborhood-poi-map
|
||||
✓ Theme-aware styling (light/dark) via useMapboxStyle() hook
|
||||
✓ CSS imported per component: 'mapbox-gl/dist/mapbox-gl.css'
|
||||
✓ SVG marker icons for POI categories (school, hospital, transit, shopping, restaurant, park)
|
||||
✓ Client-only rendering (no SSR)
|
||||
|
||||
4. CHART COMPONENTS (RECHARTS)
|
||||
✓ 6 chart types: price area, price trend, district bar, heatmap, performance, radar
|
||||
✓ Vietnamese number formatting (tr/k notation for millions)
|
||||
✓ Signal colors (green/red) via CSS variables
|
||||
✓ Responsive containers with theme-aware tooltips
|
||||
✓ Dynamic imports for performance
|
||||
|
||||
5. API ARCHITECTURE
|
||||
✓ Base client: lib/api-client.ts (127 lines)
|
||||
• CSRF token handling
|
||||
• Automatic 401 refresh-and-retry
|
||||
• Concurrent refresh coalescing
|
||||
• Type-safe generics
|
||||
✓ 12+ domain-specific API modules (listings, analytics, valuation, agents, etc.)
|
||||
✓ Both client-side and server-side implementations
|
||||
|
||||
6. STATE MANAGEMENT
|
||||
✓ Layer 1: React Query (server state caching)
|
||||
✓ Layer 2: Zustand (client UI state)
|
||||
✓ Layer 3: React Context (global features: theme, notifications, auth)
|
||||
✓ Query keys follow strict naming convention for cache invalidation
|
||||
|
||||
7. LISTING DETAIL PAGE
|
||||
✓ Server component: /[locale]/(public)/listings/[id]/page.tsx
|
||||
✓ generateMetadata() for SEO (OG, canonical, schema)
|
||||
✓ Client component: 39 KB listing-detail-client.tsx
|
||||
✓ 9 major sections with dynamic imports for heavy components
|
||||
✓ Neighborhood section uses dynamic imports (SSR: false)
|
||||
|
||||
8. PERFORMANCE OPTIMIZATIONS
|
||||
✓ Code splitting via dynamic imports (maps, charts)
|
||||
✓ Lazy loading with loading fallbacks
|
||||
✓ React Query automatic caching + deduplication
|
||||
✓ Tailwind CSS + CVA for efficient styling
|
||||
✓ Responsive containers for all visualizations
|
||||
|
||||
9. INTERNATIONALIZATION
|
||||
✓ next-intl 4.9.0 for translations
|
||||
✓ Locale prefix in routes: [locale]
|
||||
✓ Two locales: vi (Vietnamese), en (English)
|
||||
✓ Translations in /messages directory
|
||||
|
||||
10. DESIGN SYSTEM
|
||||
✓ Financial components (KPI cards, stat cards, price delta, signal display)
|
||||
✓ Data display (data table wrapper, empty states, skeletons)
|
||||
✓ Layout components (dashboard layout, density provider)
|
||||
✓ UI primitives from shadcn/ui (badge, button, card, dialog, etc.)
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📦 DEPENDENCIES SUMMARY
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
CORE FRAMEWORK:
|
||||
• Next.js 15.5.14 (App Router)
|
||||
• React 18.3.0
|
||||
|
||||
STATE & DATA:
|
||||
• @tanstack/react-query 5.96.2 (server state)
|
||||
• @tanstack/react-table 8.21.3 (table rendering)
|
||||
• zustand 5.0.12 (client state)
|
||||
|
||||
UI & STYLING:
|
||||
• Tailwind CSS 3.4.0
|
||||
• class-variance-authority 0.7.1
|
||||
• lucide-react 1.7.0 (icons)
|
||||
|
||||
FORMS & VALIDATION:
|
||||
• react-hook-form 7.72.1
|
||||
• @hookform/resolvers 5.2.2
|
||||
• zod 4.3.6
|
||||
|
||||
MAPPING & CHARTS:
|
||||
• mapbox-gl 3.21.0 ⭐
|
||||
• recharts 3.8.1 ⭐
|
||||
|
||||
UTILITIES:
|
||||
• next-intl 4.9.0 (i18n)
|
||||
• next-themes 0.4.6 (theme)
|
||||
• socket.io-client 4.8.3 (real-time)
|
||||
• html2canvas 1.4.1 + jspdf 4.2.1 (PDF export)
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🔗 COMPONENT IMPORT PATTERNS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Static imports (always in bundle):
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { useMarketReport } from '@/lib/hooks/use-analytics';
|
||||
|
||||
Dynamic imports (code-split):
|
||||
const NeighborhoodMap = dynamic(
|
||||
() => import('@/components/neighborhood').then(m => m.NeighborhoodPOIMap),
|
||||
{ ssr: false, loading: () => <Skeleton /> }
|
||||
);
|
||||
|
||||
Barrel exports (clean imports):
|
||||
// components/neighborhood/index.ts
|
||||
export { NeighborhoodRadarChart } from './neighborhood-radar-chart';
|
||||
export { NeighborhoodPOIMap } from './neighborhood-poi-map';
|
||||
|
||||
// Usage:
|
||||
import { NeighborhoodRadarChart, NeighborhoodPOIMap } from '@/components/neighborhood';
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🎯 QUICK START FOR DEVELOPMENT
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
NEXT_PUBLIC_API_URL=http://localhost:3001/api/v1
|
||||
NEXT_PUBLIC_SITE_URL=https://goodgo.vn
|
||||
MAPBOX_ACCESS_TOKEN=pk_... (from Mapbox)
|
||||
|
||||
COMMANDS:
|
||||
npm run dev ← Start dev server (port 3000)
|
||||
npm run build ← Production build
|
||||
npm run lint ← ESLint check
|
||||
npm test ← Vitest tests
|
||||
npm run typecheck ← TypeScript check
|
||||
|
||||
KEY FILES TO KNOW:
|
||||
lib/api-client.ts ← Base HTTP client
|
||||
lib/listings-api.ts ← Listings endpoints
|
||||
lib/hooks/use-analytics.ts ← React Query pattern
|
||||
components/listings/listing-detail-client.tsx ← Main detail page
|
||||
components/neighborhood/ ← Neighborhood features
|
||||
components/charts/ ← Chart components
|
||||
lib/mapbox-style.ts ← Map styling
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🔍 NOTABLE IMPLEMENTATION DETAILS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
1. CSRF PROTECTION
|
||||
└─ Token read from cookies on every request
|
||||
└─ Sent as X-CSRF-Token header for non-GET requests
|
||||
|
||||
2. AUTH TOKEN REFRESH
|
||||
└─ Automatic 401 handling with silent refresh
|
||||
└─ Concurrent requests coalesced to single refresh call
|
||||
└─ Auth endpoints excluded from refresh-retry
|
||||
|
||||
3. POI CATEGORIES
|
||||
└─ 6 types: school, hospital, transit, shopping, restaurant, park
|
||||
└─ SVG icons embedded in marker creation
|
||||
└─ Color-coded by category in POI_CATEGORY_CONFIG
|
||||
|
||||
4. NEIGHBORHOOD SCORING
|
||||
└─ 6 categories on 0-10 scale: education, healthcare, transport, shopping, dining, environment
|
||||
└─ RadarChart displays all at once with badges below
|
||||
└─ POIMap shows actual locations with distance info
|
||||
|
||||
5. PRICE FORMATTING
|
||||
└─ Vietnamese currency (VND) with tr/k notation
|
||||
└─ 1 tỷ = 1 billion (1,000,000,000)
|
||||
└─ 1 tr = 1 million (1,000,000)
|
||||
└─ 1 k = 1 thousand (1,000)
|
||||
|
||||
6. THEME SWITCHING
|
||||
└─ next-themes manages light/dark mode
|
||||
└─ Mapbox styles update reactively
|
||||
└─ CSS variables for component theming
|
||||
|
||||
7. DYNAMIC COMPONENT LOADING
|
||||
└─ Heavy components (NeighborhoodMap, charts) code-split
|
||||
└─ SSR disabled for client-only components
|
||||
└─ Loading fallback UI shown during load
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
💡 PRO TIPS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
✓ Use barrel exports (index.ts) for cleaner component imports
|
||||
✓ Always add 'enabled' condition to useQuery() for dependent queries
|
||||
✓ CSS variables (--signal-up, --color-border) support light/dark themes
|
||||
✓ React Query query keys are the foundation—get them right for caching
|
||||
✓ Mapbox GL must be in client components ('use client')
|
||||
✓ Dynamic imports reduce initial bundle size significantly
|
||||
✓ Design system provides trader-style financial components
|
||||
✓ API responses are type-safe via domain-specific API modules
|
||||
✓ SVG icons in POI markers avoid loading external font files
|
||||
✓ Responsive containers automatically adjust chart/map sizing
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📚 FILE SIZE REFERENCE
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
LARGE COMPONENTS (candidates for dynamic import):
|
||||
listing-detail-client.tsx 39 KB (main listing detail)
|
||||
listing-form-steps.tsx 14 KB (form wizard)
|
||||
neighborhood-poi-map.tsx 11 KB (Mapbox visualization)
|
||||
ai-advice-cards.tsx 9 KB (AI recommendations)
|
||||
district-heatmap.tsx 9 KB (heat visualization)
|
||||
location-picker.tsx 9 KB (map picker)
|
||||
|
||||
MEDIUM COMPONENTS:
|
||||
agent-performance.tsx 6 KB (agent metrics chart)
|
||||
price-history-chart.tsx 2 KB (inline price chart)
|
||||
|
||||
SMALL COMPONENTS:
|
||||
neighborhood-radar-chart.tsx 2 KB (radar chart)
|
||||
neighborhood-score.tsx 2 KB (score display)
|
||||
price-area-chart.tsx 2 KB (area chart)
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🚀 NEXT STEPS FOR DEVELOPMENT
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
1. Study the three generated reports in this order:
|
||||
a) ARCHITECTURE_OVERVIEW.txt (visual understanding)
|
||||
b) FRONTEND_QUICK_REFERENCE.txt (quick lookups)
|
||||
c) FRONTEND_EXPLORATION_REPORT.md (deep dive)
|
||||
|
||||
2. Familiarize yourself with key files:
|
||||
- lib/api-client.ts (understanding HTTP patterns)
|
||||
- lib/listings-api.ts (domain API example)
|
||||
- components/listings/listing-detail-client.tsx (main page structure)
|
||||
- components/neighborhood/ (neighborhood features)
|
||||
|
||||
3. Run the dev server:
|
||||
npm run dev
|
||||
Open http://localhost:3000/vi/listings/[any-id]
|
||||
|
||||
4. Explore in browser:
|
||||
- Check Network tab to see API calls
|
||||
- Toggle theme to see Mapbox style change
|
||||
- Inspect React tree to understand component hierarchy
|
||||
- Check local storage for persisted state
|
||||
|
||||
5. Experiment with modifications:
|
||||
- Add console.log to understand data flow
|
||||
- Try modifying neighborhood scores to see chart update
|
||||
- Add new query hooks following existing patterns
|
||||
- Create a test component using existing patterns
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
Questions? Reference the reports or check key file paths in QUICK_REFERENCE.txt
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
266
docs/explorations/from-desktop/README_analytics_package.md
Normal file
266
docs/explorations/from-desktop/README_analytics_package.md
Normal file
@@ -0,0 +1,266 @@
|
||||
# GoodGo Analytics Module — Complete Architecture Guide
|
||||
|
||||
## 📚 Overview
|
||||
|
||||
This package contains **4 comprehensive guides** (2,174 lines, 71 KB) to understand the GoodGo Analytics Module architecture, caching system, and endpoint patterns.
|
||||
|
||||
## 📖 Quick Navigation
|
||||
|
||||
| File | Purpose | Read Time | Best For |
|
||||
|------|---------|-----------|----------|
|
||||
| **00_SUMMARY.md** | Overview & key findings | 5 min | Getting oriented |
|
||||
| **02_quick_reference.md** | Visual diagrams & checklists | 10 min | Visual learners |
|
||||
| **03_file_paths_reference.md** | File navigation & structure | 15 min | Finding code |
|
||||
| **01_analytics_architecture_guide.md** | Deep dive with code examples | 30+ min | Full understanding |
|
||||
|
||||
## 🚀 Recommended Reading Path
|
||||
|
||||
1. Start with **00_SUMMARY.md** — understand what you're learning
|
||||
2. Read **02_quick_reference.md** — visualize the architecture
|
||||
3. Skim **03_file_paths_reference.md** — know where to find things
|
||||
4. Deep dive **01_analytics_architecture_guide.md** — master the patterns
|
||||
|
||||
**Total time: ~60 minutes to fully understand the module**
|
||||
|
||||
## 🎯 What You'll Learn
|
||||
|
||||
### Architecture
|
||||
- ✅ Domain-Driven Design (DDD) with CQRS pattern
|
||||
- ✅ 4-layer structure: Presentation → Application → Domain → Infrastructure
|
||||
- ✅ 24 endpoints across 2 controllers
|
||||
- ✅ 15+ query handlers with caching
|
||||
|
||||
### Caching
|
||||
- ✅ Redis cache-aside pattern
|
||||
- ✅ Two caching styles: @Cacheable decorator vs manual cache.getOrSet()
|
||||
- ✅ Cache TTLs for different endpoint types
|
||||
- ✅ Graceful degradation when Redis is down
|
||||
- ✅ Cache metadata in responses
|
||||
|
||||
### Database
|
||||
- ✅ Prisma schema for Property, Listing, MarketIndex, Valuation
|
||||
- ✅ PostGIS spatial queries
|
||||
- ✅ Indexes for query optimization
|
||||
|
||||
### Implementation
|
||||
- ✅ How to add a new GET endpoint in 7 steps
|
||||
- ✅ Query handler patterns with real examples
|
||||
- ✅ DTO design and validation
|
||||
- ✅ Error handling conventions
|
||||
- ✅ Testing patterns
|
||||
|
||||
## 🏗️ Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ PRESENTATION LAYER │
|
||||
│ • AnalyticsController (19 endpoints) │
|
||||
│ • AvmController (5 endpoints) │
|
||||
│ • DTOs, Guards, Interceptors │
|
||||
└──────────────────┬──────────────────────┘
|
||||
↓ QueryBus.execute()
|
||||
┌──────────────────────────────────────────┐
|
||||
│ APPLICATION LAYER (CQRS) │
|
||||
│ • 15+ Query Handlers (@QueryHandler) │
|
||||
│ • @Cacheable or cache.getOrSet() │
|
||||
│ • Try-catch error handling │
|
||||
└──────────────────┬──────────────────────┘
|
||||
↓ Injected services
|
||||
┌──────────────────────────────────────────┐
|
||||
│ DOMAIN LAYER (Business Logic) │
|
||||
│ • Entities, Repository Interfaces │
|
||||
│ • Service Interfaces, Result DTOs │
|
||||
└──────────────────┬──────────────────────┘
|
||||
↓ Injected implementation
|
||||
┌──────────────────────────────────────────┐
|
||||
│ INFRASTRUCTURE LAYER │
|
||||
│ • Prisma Repositories │
|
||||
│ • HTTP Services, External clients │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 💾 Caching Pattern
|
||||
|
||||
```
|
||||
HTTP GET /analytics/market-snapshot?city=Ho Chi Minh
|
||||
↓
|
||||
QueryHandler builds key: "cache:analytics:market_snapshot:ho_chi_minh"
|
||||
↓
|
||||
cache.getOrSet(key, loader, TTL, resource):
|
||||
├─ Redis HIT: return cached value
|
||||
├─ Redis MISS: call loader() → compute → store
|
||||
└─ Redis DOWN: call loader() directly (graceful degradation)
|
||||
↓
|
||||
CacheMetaInterceptor wraps: { data: result, cacheMeta: {...} }
|
||||
↓
|
||||
HTTP 200: { data: {...}, cacheMeta: { cachedAt, nextRefreshAt, source } }
|
||||
```
|
||||
|
||||
## 🔑 Key Findings
|
||||
|
||||
- **24 Endpoints**: 19 in AnalyticsController + 5 in AvmController
|
||||
- **15+ Query Handlers**: All with caching (except predictions)
|
||||
- **2 Caching Patterns**: @Cacheable decorator or manual cache.getOrSet()
|
||||
- **Redis Cache-Aside**: TTL-based expiry, graceful degradation
|
||||
- **4 Prisma Models**: Property, Listing, MarketIndex, Valuation
|
||||
- **DTOs + Guards**: All endpoints secured with JWT, Quota, Rate Limit
|
||||
|
||||
## 📚 Detailed Contents
|
||||
|
||||
### 00_SUMMARY.md
|
||||
- Overview of all 4 guides
|
||||
- Key architecture decisions
|
||||
- 7-step endpoint addition walkthrough
|
||||
- Architecture decision matrix
|
||||
- Core conventions checklist
|
||||
|
||||
### 01_analytics_architecture_guide.md ⭐ COMPREHENSIVE
|
||||
- DDD layer breakdown with code examples
|
||||
- All 24 endpoints documented
|
||||
- Query handler patterns (decorator vs manual)
|
||||
- Complete Redis caching system
|
||||
- Real code from GetMarketSnapshotHandler, GetDistrictStatsHandler
|
||||
- Full Prisma schema documentation
|
||||
- Shared module utilities (CacheService, @Cacheable, CacheMetaInterceptor)
|
||||
- Complete 7-step guide: adding GET /trending-areas endpoint
|
||||
- Testing patterns & error handling conventions
|
||||
|
||||
### 02_quick_reference.md ⭐ VISUAL QUICK START
|
||||
- Architecture layer stack diagram
|
||||
- Request flow visualization
|
||||
- Caching strategy matrix
|
||||
- Decorators & guards cheat sheet
|
||||
- Prisma schema snapshot
|
||||
- Response structure examples
|
||||
- 7-step endpoint addition checklist
|
||||
|
||||
### 03_file_paths_reference.md ⭐ NAVIGATION MAP
|
||||
- Core module files (analytics.module.ts, index.ts)
|
||||
- All 24 endpoints mapped to file paths
|
||||
- DTO files organized by type (request vs response)
|
||||
- All 15+ query types with descriptions
|
||||
- Domain, Infrastructure, Shared layer breakdowns
|
||||
- Database schema models with fields & indexes
|
||||
- Directory tree with line counts
|
||||
- Import patterns reference
|
||||
- Key metrics & numbers
|
||||
|
||||
## 🎯 Quick Start: Adding New Endpoint
|
||||
|
||||
### 7 Steps to Add GET /analytics/trending-areas
|
||||
|
||||
1. **Request DTO** → `presentation/dto/get-trending-areas.dto.ts`
|
||||
2. **Query Class** → `application/queries/get-trending-areas/query.ts`
|
||||
3. **Handler** → `application/queries/get-trending-areas/handler.ts` (with @Cacheable)
|
||||
4. **Register** → Add to QueryHandlers array in analytics.module.ts
|
||||
5. **Controller** → Add method to analytics.controller.ts
|
||||
6. **Export** → Add to presentation/dto/index.ts
|
||||
7. **Test** → Create handler spec
|
||||
|
||||
Full walkthrough with code in guides!
|
||||
|
||||
## ✅ Core Conventions
|
||||
|
||||
```ts
|
||||
// Cache with TTL
|
||||
Dashboard tiles: 300s (5 min)
|
||||
Aggregations: 300s (5 min)
|
||||
Reports: 900s (15 min)
|
||||
Trends: 1800s (30 min)
|
||||
Predictions: NO_CACHE (always fresh)
|
||||
|
||||
// Build cache keys deterministically
|
||||
CacheService.buildKey(CachePrefix.MARKET_DISTRICT, city, period)
|
||||
// Result: "cache:market:district:ho_chi_minh:2024_q1"
|
||||
|
||||
// Always wrap handlers in try-catch
|
||||
try { ... } catch(error) {
|
||||
if (error instanceof DomainException) throw error;
|
||||
logger.error(...);
|
||||
throw new InternalServerErrorException('...');
|
||||
}
|
||||
|
||||
// Always return DTOs with null metadata
|
||||
return { city, data, cachedAt: null, nextRefreshAt: null };
|
||||
|
||||
// Always use guards
|
||||
@UseGuards(JwtAuthGuard, QuotaGuard)
|
||||
@RequireQuota('analytics_queries')
|
||||
```
|
||||
|
||||
## 📊 Key Metrics
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Controllers | 2 |
|
||||
| Endpoints | 24 |
|
||||
| Query Handlers | 15+ |
|
||||
| DTOs | 15+ |
|
||||
| Caching Patterns | 2 |
|
||||
| Cache Prefixes | 10+ |
|
||||
| Cache TTLs | 20+ |
|
||||
| Prisma Models | 4 |
|
||||
| Repository Interfaces | 2 |
|
||||
| Services (interfaces) | 2 |
|
||||
| Services (implementations) | 6+ |
|
||||
| Module LOC | ~2,000 |
|
||||
| Shared Cache LOC | ~250 |
|
||||
|
||||
## 🧠 Test Your Understanding
|
||||
|
||||
After reading all guides, you should be able to:
|
||||
|
||||
1. Explain the 4 DDD layers and what goes in each
|
||||
2. Describe how cache-aside pattern works
|
||||
3. Distinguish @Cacheable from cache.getOrSet()
|
||||
4. Explain why response DTOs have null metadata
|
||||
5. Build deterministic cache keys
|
||||
6. Choose appropriate TTLs for endpoints
|
||||
7. List required guards for endpoints
|
||||
8. Add a new endpoint in 7 steps
|
||||
9. Explain CacheMetaInterceptor purpose
|
||||
10. Describe graceful degradation behavior
|
||||
|
||||
## 📁 Prisma Schema Models
|
||||
|
||||
**Property** — Real estate property details
|
||||
- Type, location (PostGIS), area, rooms, condition
|
||||
- Indexes: [propertyType], [district, city], [location (Gist)]
|
||||
|
||||
**Listing** — Property listings on platform
|
||||
- Price (BigInt), status, AVM estimates
|
||||
- Indexes: [status], [sellerId, status], [publishedAt]
|
||||
|
||||
**MarketIndex** — Aggregated market data by period
|
||||
- District, city, propertyType, period
|
||||
- Median price, average price/m², statistics
|
||||
- Unique: [district, city, propertyType, period]
|
||||
|
||||
**Valuation** — AI valuation estimates
|
||||
- Property, estimated price, confidence, method
|
||||
- Features, comparables, explainers (Json)
|
||||
- Index: [propertyId, valuationDate DESC]
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
1. Read all 4 guides in recommended order (60 min)
|
||||
2. Review the 7-step endpoint addition guide
|
||||
3. Study real handler code examples in guide
|
||||
4. Explore actual files in codebase:
|
||||
- `apps/api/src/modules/analytics/`
|
||||
- `apps/api/src/modules/shared/`
|
||||
5. Add your first new endpoint following patterns
|
||||
|
||||
## 📞 Questions & Understanding
|
||||
|
||||
If you have questions after reading:
|
||||
- Refer back to specific sections in guides
|
||||
- Check code examples in guide vs actual code
|
||||
- Review the 10 validation questions
|
||||
- Study the architecture decision matrix
|
||||
|
||||
---
|
||||
|
||||
**Total package: 2,174 lines, 71 KB of comprehensive documentation**
|
||||
|
||||
Start with 00_SUMMARY.md and follow the reading path. You'll understand the entire architecture in about 60 minutes!
|
||||
Reference in New Issue
Block a user