import { parseUserAgent } from '@openpanel/common/server'; import { getSalts } from '@openpanel/db'; import { getGeoLocation } from '@openpanel/geo'; import { type LogsQueuePayload, logsQueue } from '@openpanel/queue'; import { type ILogBatchPayload, zLogBatchPayload } from '@openpanel/validation'; import type { FastifyReply, FastifyRequest } from 'fastify'; import { getDeviceId } from '@/utils/ids'; import { getStringHeaders } from './track.controller'; export async function handler( request: FastifyRequest<{ Body: ILogBatchPayload }>, reply: FastifyReply, ) { const projectId = request.client?.projectId; if (!projectId) { return reply.status(400).send({ status: 400, error: 'Missing projectId' }); } const validationResult = zLogBatchPayload.safeParse(request.body); if (!validationResult.success) { return reply.status(400).send({ status: 400, error: 'Bad Request', message: 'Validation failed', errors: validationResult.error.errors, }); } const { logs } = validationResult.data; const ip = request.clientIp; const ua = request.headers['user-agent'] ?? 'unknown/1.0'; const headers = getStringHeaders(request.headers); const receivedAt = new Date().toISOString(); const [geo, salts] = await Promise.all([getGeoLocation(ip), getSalts()]); const { deviceId, sessionId } = await getDeviceId({ projectId, ip, ua, salts }); const uaInfo = parseUserAgent(ua, undefined); const jobs: LogsQueuePayload[] = logs.map((log) => ({ type: 'incomingLog' as const, payload: { projectId, log: { ...log, timestamp: log.timestamp ?? receivedAt, }, uaInfo, geo: { country: geo.country, city: geo.city, region: geo.region, }, headers, deviceId, sessionId, }, })); await logsQueue.addBulk( jobs.map((job) => ({ name: 'incomingLog', data: job, })), ); return reply.status(200).send({ ok: true, count: logs.length }); }