53e0db6a98
Add log_promo_event for scan/attribution/register/order with IP; admin metrics APIs and ECharts on promo detail page. Co-authored-by: Cursor <cursoragent@cursor.com>
114 lines
3.6 KiB
TypeScript
114 lines
3.6 KiB
TypeScript
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;
|
|
}
|
|
}
|