/ server / api / external / heartbeats.post.ts
heartbeats.post.ts
 1  import { PrismaClient } from "@prisma/client";
 2  import { H3Event } from "h3";
 3  import { z } from "zod";
 4  import { handleApiError } from "~~/server/utils/logging";
 5  
 6  const prisma = new PrismaClient({
 7    log: ['warn', 'error'],
 8  });
 9  
10  const apiKeySchema = z.string().uuid();
11  
12  const heartbeatSchema = z.object({
13    timestamp: z.string().datetime().or(z.number()),
14    project: z.string().min(1).max(255),
15    language: z.string().min(1).max(50),
16    editor: z.string().min(1).max(50),
17    os: z.string().min(1).max(50),
18    branch: z.string().max(255).optional(),
19    file: z.string().max(255),
20  });
21  
22  export default defineEventHandler(async (event: H3Event) => {
23    try {
24      const authHeader = getHeader(event, "authorization");
25      if (!authHeader || !authHeader.startsWith("Bearer ")) {
26        throw handleApiError(401, "Heartbeat API error: Missing or invalid API key format in header.");
27      }
28  
29      const apiKey = authHeader.substring(7);
30      const validationResult = apiKeySchema.safeParse(apiKey);
31  
32      if (!validationResult.success) {
33        throw handleApiError(401, `Heartbeat API error: Invalid API key format. Key: ${apiKey.substring(0,4)}...`);
34      }
35  
36      const user = await prisma.user.findUnique({
37        where: { apiKey },
38        select: { id: true, apiKey: true },
39      });
40  
41      if (!user || user.apiKey !== apiKey) {
42        throw handleApiError(401, `Heartbeat API error: Invalid API key. Key: ${apiKey.substring(0,4)}...`);
43      }
44  
45      const body = await readBody(event);
46      const validatedData = heartbeatSchema.parse(body);
47  
48      const timestamp =
49        typeof validatedData.timestamp === "number"
50          ? BigInt(validatedData.timestamp)
51          : BigInt(new Date(validatedData.timestamp).getTime());
52  
53      const heartbeat = await prisma.heartbeats.create({
54        data: {
55          userId: user.id,
56          timestamp,
57          project: validatedData.project,
58          language: validatedData.language,
59          editor: validatedData.editor,
60          os: validatedData.os,
61          branch: validatedData.branch,
62          file: validatedData.file,
63        },
64      });
65  
66      return {
67        success: true,
68        id: heartbeat.id,
69      };
70    } catch (error: any) {
71      if (error && typeof error === "object" && error.statusCode) throw error;
72      if (error instanceof z.ZodError) {
73        throw handleApiError(400, `Heartbeat API error: Validation error. Details: ${error.errors[0].message}`);
74      }
75      const detailedMessage = error instanceof Error ? error.message : "An unknown error occurred processing heartbeat.";
76      const apiKeyPrefix = getHeader(event, "authorization")?.substring(7,11) || "UNKNOWN";
77      throw handleApiError(500, `Heartbeat API error: Failed to process heartbeat. API Key prefix: ${apiKeyPrefix}... Error: ${detailedMessage}`, "Failed to process your request.");
78    }
79  });