import { BadRequestException, Body, Controller, Post, Req, UseGuards } from '@nestjs/common'; import type { Request } from 'express'; import { AnalyticsService } from './analytics.service'; import { PromoCodeService } from '../promo/promo-code.service'; import { extractClientIp } from '../../common/geo/client-ip.util'; import { JwtAuthGuard, AuthUser } from '../../common/guards/jwt-auth.guard'; import { OptionalJwtAuthGuard } from '../../common/guards/optional-jwt-auth.guard'; import { CurrentUser } from '../../common/decorators/current-user.decorator'; import { PromoTouchDto } from './dto/promo.dto'; import { TrackPartnerEventsDto, TrackStoreEventsDto, TrackUserEventsDto, } from './dto/track-events.dto'; import { ActorType, ClientApp } from '@dukang/shared-types'; @Controller('analytics') export class AnalyticsController { constructor(private readonly analyticsService: AnalyticsService) {} @Post('events') @UseGuards(OptionalJwtAuthGuard) track(@CurrentUser() user: AuthUser | undefined, @Body() body: TrackUserEventsDto) { const userId = user?.actorType === ActorType.USER ? user.actorId : undefined; const clientApp = user?.clientApp ?? body.clientApp ?? ClientApp.USER_H5; return this.analyticsService.trackBatchOptional(userId, clientApp, body.events, body.sessionId); } @Post('store-events') @UseGuards(JwtAuthGuard) trackStore(@CurrentUser() user: AuthUser, @Body() body: TrackStoreEventsDto) { if (user.actorType !== ActorType.STORE) { throw new BadRequestException('仅门店端可上报'); } const storeId = body.storeId ? BigInt(body.storeId) : user.storeId; if (storeId == null) throw new BadRequestException('缺少门店信息'); return this.analyticsService.trackStoreBatch( user.actorId, storeId, user.clientApp, body.events, body.sessionId, ); } @Post('partner-events') @UseGuards(JwtAuthGuard) trackPartner(@CurrentUser() user: AuthUser, @Body() body: TrackPartnerEventsDto) { if (user.actorType !== ActorType.PARTNER) { throw new BadRequestException('仅合伙人端可上报'); } return this.analyticsService.trackPartnerBatch( user.actorId, user.actorId, user.clientApp, body.events, body.sessionId, ); } } @Controller('promo') export class PromoController { constructor( private readonly promoCodeService: PromoCodeService, private readonly analyticsService: AnalyticsService, ) {} @Post('touch') @UseGuards(OptionalJwtAuthGuard) async touch( @CurrentUser() user: AuthUser | undefined, @Body() dto: PromoTouchDto, @Req() req: Request, ) { const userId = user?.actorType === ActorType.USER ? user.actorId : undefined; const clientIp = extractClientIp(req) ?? undefined; const result = await this.promoCodeService.touch( { promoCode: dto.promoCode, qrcodeId: dto.qrcodeId, promoId: dto.promoId, countScan: dto.countScan, }, userId, { clientIp, sessionId: dto.sessionId }, ); void this.analyticsService.trackBatchOptional( userId, user?.clientApp ?? ClientApp.USER_H5, [ { eventName: 'promo_touch', params: { sessionId: dto.sessionId, promoCode: result.promoCode, promoCodeId: result.promoCodeId, channelName: result.channelName, attributed: result.attributed, sourceApplied: result.sourceApplied, scanCounted: result.scanCounted, sourceType: 'PROMO_CODE', sourceRefId: result.promoCodeId, }, }, ], dto.sessionId, ); return result; } }