Files
goodgo-platform/apps/api/src/modules/analytics/presentation/controllers/analytics.controller.ts
Ho Ngoc Hai 25420720e7 fix(api,ci): remove type-only imports for DI and isolate CI ports from dev
- Remove `type` keyword from NestJS injectable class imports across all
  modules to fix runtime DI resolution (330+ handler/listener files)
- Offset CI docker-compose ports (5433/6380/8109/9002) to avoid
  conflicts with running dev containers
- Update .env.test, playwright.config.ts, and e2e workflow to use
  isolated CI ports with configurable overrides
- Fix prisma/seed.ts to use deterministic IDs for Prisma 7 upsert
  compatibility (phoneHash replaced phone as unique index)
- Add dedicated Docker bridge network for CI service containers

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
2026-04-13 01:40:14 +07:00

100 lines
4.4 KiB
TypeScript

import {
Controller,
Get,
Query,
UseGuards,
} from '@nestjs/common';
import { QueryBus } from '@nestjs/cqrs';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard } from '@modules/auth';
import { RequireQuota, QuotaGuard } from '@modules/subscriptions';
import { DistrictStatsDto } from '../../application/queries/get-district-stats/get-district-stats.handler';
import { GetDistrictStatsQuery } from '../../application/queries/get-district-stats/get-district-stats.query';
import { HeatmapDto } from '../../application/queries/get-heatmap/get-heatmap.handler';
import { GetHeatmapQuery } from '../../application/queries/get-heatmap/get-heatmap.query';
import { MarketReportDto } from '../../application/queries/get-market-report/get-market-report.handler';
import { GetMarketReportQuery } from '../../application/queries/get-market-report/get-market-report.query';
import { PriceTrendDto } from '../../application/queries/get-price-trend/get-price-trend.handler';
import { GetPriceTrendQuery } from '../../application/queries/get-price-trend/get-price-trend.query';
import { ValuationDto } from '../../application/queries/get-valuation/get-valuation.handler';
import { GetValuationQuery } from '../../application/queries/get-valuation/get-valuation.query';
import { GetDistrictStatsDto } from '../dto/get-district-stats.dto';
import { GetHeatmapDto } from '../dto/get-heatmap.dto';
import { GetMarketReportDto } from '../dto/get-market-report.dto';
import { GetPriceTrendDto } from '../dto/get-price-trend.dto';
import { GetValuationDto } from '../dto/get-valuation.dto';
@ApiTags('analytics')
@Controller('analytics')
export class AnalyticsController {
constructor(
private readonly queryBus: QueryBus,
) {}
@ApiBearerAuth('JWT')
@UseGuards(JwtAuthGuard, QuotaGuard)
@RequireQuota('analytics_queries')
@Get('market-report')
@ApiOperation({ summary: 'Get market report for a city' })
@ApiResponse({ status: 200, description: 'Market report retrieved' })
@ApiResponse({ status: 403, description: 'Quota exceeded' })
async getMarketReport(@Query() dto: GetMarketReportDto): Promise<MarketReportDto> {
return this.queryBus.execute(
new GetMarketReportQuery(dto.city, dto.period, dto.propertyType),
);
}
@ApiBearerAuth('JWT')
@UseGuards(JwtAuthGuard, QuotaGuard)
@RequireQuota('analytics_queries')
@Get('price-trend')
@ApiOperation({ summary: 'Get price trend for a district' })
@ApiResponse({ status: 200, description: 'Price trend data retrieved' })
@ApiResponse({ status: 403, description: 'Quota exceeded' })
async getPriceTrend(@Query() dto: GetPriceTrendDto): Promise<PriceTrendDto> {
return this.queryBus.execute(
new GetPriceTrendQuery(dto.district, dto.city, dto.propertyType, dto.periods),
);
}
@ApiBearerAuth('JWT')
@UseGuards(JwtAuthGuard, QuotaGuard)
@RequireQuota('analytics_queries')
@Get('heatmap')
@ApiOperation({ summary: 'Get price heatmap for a city' })
@ApiResponse({ status: 200, description: 'Heatmap data retrieved' })
@ApiResponse({ status: 403, description: 'Quota exceeded' })
async getHeatmap(@Query() dto: GetHeatmapDto): Promise<HeatmapDto> {
return this.queryBus.execute(
new GetHeatmapQuery(dto.city, dto.period),
);
}
@ApiBearerAuth('JWT')
@UseGuards(JwtAuthGuard, QuotaGuard)
@RequireQuota('analytics_queries')
@Get('district-stats')
@ApiOperation({ summary: 'Get statistics by district' })
@ApiResponse({ status: 200, description: 'District statistics retrieved' })
@ApiResponse({ status: 403, description: 'Quota exceeded' })
async getDistrictStats(@Query() dto: GetDistrictStatsDto): Promise<DistrictStatsDto> {
return this.queryBus.execute(
new GetDistrictStatsQuery(dto.city, dto.period),
);
}
@ApiBearerAuth('JWT')
@UseGuards(JwtAuthGuard, QuotaGuard)
@RequireQuota('analytics_queries')
@Get('valuation')
@ApiOperation({ summary: 'Get automated property valuation (AVM)' })
@ApiResponse({ status: 200, description: 'Valuation estimate retrieved' })
@ApiResponse({ status: 400, description: 'Invalid parameters — provide propertyId or (lat, lng, areaM2)' })
@ApiResponse({ status: 403, description: 'Quota exceeded' })
async getValuation(@Query() dto: GetValuationDto): Promise<ValuationDto> {
return this.queryBus.execute(
new GetValuationQuery(dto.propertyId, dto.latitude, dto.longitude, dto.areaM2, dto.propertyType),
);
}
}