fix(platform-service): harden broadcast metrics and export job lifecycle

This commit is contained in:
saravanakumardb1 2026-03-22 11:57:47 -07:00
parent dda38aa009
commit 265599d005
3 changed files with 40 additions and 18 deletions

View File

@ -324,7 +324,7 @@ export async function recordReadReceipt(
userId: string,
productId: string,
action: 'read' | 'click' | 'dismiss'
): Promise<void> {
): Promise<boolean> {
const container = getContainer('broadcast_reads');
const id = `${broadcastId}:${userId}`;
const now = new Date().toISOString();
@ -334,6 +334,13 @@ export async function recordReadReceipt(
if (existing) {
const receipt = existing as BroadcastRead;
const alreadyRecorded =
(action === 'read' && !!receipt.readAt) ||
(action === 'click' && !!receipt.clickedAt) ||
(action === 'dismiss' && !!receipt.dismissedAt);
if (alreadyRecorded) {
return false;
}
const updates: Partial<BroadcastRead> & { updatedAt: string } = {
updatedAt: now,
};
@ -345,6 +352,7 @@ export async function recordReadReceipt(
...receipt,
...updates,
});
return true;
} else {
const receipt: BroadcastRead = {
id,
@ -357,6 +365,7 @@ export async function recordReadReceipt(
createdAt: now,
};
await container.items.create(receipt);
return true;
}
} catch (err) {
if ((err as { code?: number }).code === 404) {
@ -372,6 +381,7 @@ export async function recordReadReceipt(
createdAt: now,
};
await container.items.create(receipt);
return true;
} else {
throw err;
}

View File

@ -3,6 +3,7 @@
* @module broadcasts/routes
*/
import { randomUUID } from 'node:crypto';
import type { FastifyInstance } from 'fastify';
import {
UnauthorizedError,
@ -78,7 +79,7 @@ async function adminRoutes(app: FastifyInstance): Promise<void> {
const now = new Date().toISOString();
const broadcast: Broadcast = {
id: `bcast_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
id: `bcast_${randomUUID()}`,
productId,
...input,
status: input.scheduledAt ? BroadcastStatus.SCHEDULED : BroadcastStatus.DRAFT,
@ -317,12 +318,12 @@ async function adminRoutes(app: FastifyInstance): Promise<void> {
const now = new Date().toISOString();
const cloned: Broadcast = {
...existing,
id: `bcast_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
id: `bcast_${randomUUID()}`,
title: `${existing.title} (Clone)`,
status: BroadcastStatus.DRAFT,
variant: variant ?? 'treatment',
parentBroadcastId: existing.id,
experimentId: existing.experimentId ?? `exp_${Date.now()}`,
experimentId: existing.experimentId ?? `exp_${randomUUID()}`,
metrics: {
targetedCount: 0,
sentCount: 0,
@ -380,12 +381,14 @@ async function publicRoutes(app: FastifyInstance): Promise<void> {
await repo.updateInAppMessageStatus(id, userId, 'read');
// Record read receipt
await repo.recordReadReceipt(message.broadcastId, userId, productId, 'read');
const recorded = await repo.recordReadReceipt(message.broadcastId, userId, productId, 'read');
// Update broadcast metrics
await repo.updateBroadcastMetrics(message.broadcastId, productId, {
openedCount: 1, // Will be incremented properly in real implementation
});
if (recorded) {
await repo.updateBroadcastMetrics(message.broadcastId, productId, {
openedCount: 1,
});
}
return { success: true };
});
@ -401,11 +404,18 @@ async function publicRoutes(app: FastifyInstance): Promise<void> {
if (!message) throw new NotFoundError('Message not found');
await repo.updateInAppMessageStatus(id, userId, 'dismissed');
await repo.recordReadReceipt(message.broadcastId, userId, productId, 'dismiss');
const recorded = await repo.recordReadReceipt(
message.broadcastId,
userId,
productId,
'dismiss'
);
await repo.updateBroadcastMetrics(message.broadcastId, productId, {
dismissedCount: 1,
});
if (recorded) {
await repo.updateBroadcastMetrics(message.broadcastId, productId, {
dismissedCount: 1,
});
}
return { success: true };
});
@ -420,11 +430,13 @@ async function publicRoutes(app: FastifyInstance): Promise<void> {
const message = messages.find(m => m.id === id);
if (!message) throw new NotFoundError('Message not found');
await repo.recordReadReceipt(message.broadcastId, userId, productId, 'click');
const recorded = await repo.recordReadReceipt(message.broadcastId, userId, productId, 'click');
await repo.updateBroadcastMetrics(message.broadcastId, productId, {
clickedCount: 1,
});
if (recorded) {
await repo.updateBroadcastMetrics(message.broadcastId, productId, {
clickedCount: 1,
});
}
return { success: true, redirectUrl: message.ctaUrl };
});

View File

@ -64,7 +64,7 @@ export async function exportRoutes(app: FastifyInstance) {
const log = req.log;
process.nextTick(async () => {
try {
await repo.updateExportJob({
const processingJob = await repo.updateExportJob({
...created,
status: 'processing',
startedAt: new Date().toISOString(),
@ -73,7 +73,7 @@ export async function exportRoutes(app: FastifyInstance) {
const serialized = created.format === 'json' ? JSON.stringify(rows, null, 2) : toCsv(rows);
const fileName = `${created.type}-${access.productId}-${Date.now()}.${created.format}`;
await repo.updateExportJob({
...created,
...processingJob,
status: 'ready',
data: serialized,
rowCount: rows.length,