feat(promo): promo metric event logs and HQ timeline charts (v3.4.13)
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>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { PromoMetricEventType } from '@dukang/shared-types';
|
||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import { IpGeoService } from '../../common/geo/ip-geo.service';
|
||||
|
||||
export type PromoMetricLogInput = {
|
||||
promoCodeId: bigint;
|
||||
eventType: PromoMetricEventType;
|
||||
userId?: bigint;
|
||||
orderId?: bigint;
|
||||
sessionId?: string;
|
||||
clientIp?: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class PromoMetricLogService {
|
||||
private readonly logger = new Logger(PromoMetricLogService.name);
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly ipGeoService: IpGeoService,
|
||||
) {}
|
||||
|
||||
logEvent(input: PromoMetricLogInput): void {
|
||||
void this.writeEvent(input).catch((err) => {
|
||||
this.logger.debug(
|
||||
`promo metric log failed: ${err instanceof Error ? err.message : err}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private async writeEvent(input: PromoMetricLogInput): Promise<void> {
|
||||
const clientIp = input.clientIp?.trim() || null;
|
||||
const geo = clientIp ? this.ipGeoService.resolve(clientIp) : { province: null, city: null };
|
||||
|
||||
await this.prisma.logPromoEvent.create({
|
||||
data: {
|
||||
promoCodeId: input.promoCodeId,
|
||||
eventType: input.eventType,
|
||||
userId: input.userId ?? null,
|
||||
orderId: input.orderId ?? null,
|
||||
sessionId: input.sessionId?.trim() || null,
|
||||
clientIp,
|
||||
ipProvince: geo.province,
|
||||
ipCity: geo.city,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user