Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | /** * Analytics Session API Route * * Handles session creation and heartbeat updates. * POST - Create new session * PATCH - Update session (heartbeat, user identification) */ import { NextRequest, NextResponse } from 'next/server'; import { z } from "zod"; import { prisma } from "@/lib/prisma"; import { parseUserAgent, getDeviceType } from "@/lib/analytics/utils"; import { withErrorHandling, successResponse, createdResponse, ApiError, ApiSuccessResponse, ApiErrorResponse } from "@/lib/api"; // Schema for creating a new session const createSessionSchema = z.object({ visitorId: z.string().min(1).max(50), userId: z.number().optional(), landingPage: z.string().max(500), referrer: z.string().max(500).optional(), source: z.string().max(100).optional(), medium: z.string().max(100).optional(), campaign: z.string().max(200).optional()}); // Schema for session heartbeat/update const updateSessionSchema = z.object({ sessionId: z.string().min(1), userId: z.number().optional()}); /** * POST - Create a new session */ async function handlePost( request: NextRequest ): Promise<NextResponse<ApiSuccessResponse<{ sessionId: string }> | ApiErrorResponse>> { const body = await request.json(); const data = createSessionSchema.parse(body); const userAgent = request.headers.get("user-agent") || ""; const { browser, os } = parseUserAgent(userAgent); const deviceType = getDeviceType(userAgent); const session = await prisma.analyticsSession.create({ data: { visitorId: data.visitorId, userId: data.userId, landingPage: data.landingPage, referrer: data.referrer, source: data.source, medium: data.medium, campaign: data.campaign, browser, os, deviceType, pageViews: 0, // Will be incremented on first pageview }}); return createdResponse({ sessionId: session.id }); } export const POST = withErrorHandling(handlePost); /** * PATCH - Update session (heartbeat or user identification) */ async function handlePatch( request: NextRequest ): Promise<NextResponse<ApiSuccessResponse<Record<string, never>> | ApiErrorResponse>> { const body = await request.json(); const data = updateSessionSchema.parse(body); // Build update data const updateData: { lastSeenAt: Date; duration?: { increment: number }; userId?: number; } = { lastSeenAt: new Date(), duration: { increment: 30 }, // Heartbeat every 30 seconds }; // If userId provided, update it (for when user logs in) if (data.userId) { updateData.userId = data.userId; } try { await prisma.analyticsSession.update({ where: { id: data.sessionId }, data: updateData}); } catch (error) { // Session not found is not an error we need to log if ( error instanceof Error && error.message.includes("Record to update not found") ) { throw ApiError.notFound("Session"); } throw error; } return successResponse({}); } export const PATCH = withErrorHandling(handlePatch); |