diff --git a/apps/webapp/app/routes/api.v1.usage.ingest.ts b/apps/webapp/app/routes/api.v1.usage.ingest.ts index b4c165c8d..20a824bf7 100644 --- a/apps/webapp/app/routes/api.v1.usage.ingest.ts +++ b/apps/webapp/app/routes/api.v1.usage.ingest.ts @@ -1,24 +1,6 @@ import { ActionFunctionArgs } from "@remix-run/server-runtime"; -import { MachinePresetName } from "@trigger.dev/core/v3"; -import { z } from "zod"; -import { prisma } from "~/db.server"; -import { validateJWTTokenAndRenew } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; -import { workerQueue } from "~/services/worker.server"; -import { machinePresetFromName } from "~/v3/machinePresets.server"; -import { reportUsageEvent } from "~/v3/openMeter.server"; - -const JWTPayloadSchema = z.object({ - environment_id: z.string(), - org_id: z.string(), - project_id: z.string(), - run_id: z.string(), - machine_preset: z.string(), -}); - -const BodySchema = z.object({ - durationMs: z.number(), -}); +import { reportComputeUsage } from "~/services/platform.v3.server"; export async function action({ request }: ActionFunctionArgs) { // Ensure this is a POST request @@ -26,74 +8,10 @@ export async function action({ request }: ActionFunctionArgs) { return { status: 405, body: "Method Not Allowed" }; } - const jwtResult = await validateJWTTokenAndRenew(request, JWTPayloadSchema); - - if (!jwtResult) { - return { status: 401, body: "Unauthorized" }; + try { + return await reportComputeUsage(request); + } catch (e) { + logger.error("Error reporting compute usage", { error: e }); + return new Response(null, { status: 500 }); } - - const rawJson = await request.json(); - - const json = BodySchema.safeParse(rawJson); - - if (!json.success) { - logger.error("Failed to parse request body", { rawJson }); - - return { status: 400, body: "Bad Request" }; - } - - const preset = machinePresetFromName(jwtResult.payload.machine_preset as MachinePresetName); - - logger.debug("[/api/v1/usage/ingest] Reporting usage", { jwtResult, json: json.data, preset }); - - if (json.data.durationMs > 0) { - const costInCents = json.data.durationMs * preset.centsPerMs; - - const taskRun = await prisma.taskRun.update({ - where: { - id: jwtResult.payload.run_id, - }, - data: { - usageDurationMs: { - increment: json.data.durationMs, - }, - costInCents: { - increment: json.data.durationMs * preset.centsPerMs, - }, - }, - }); - - try { - await reportUsageEvent({ - source: "webapp", - type: "usage", - subject: jwtResult.payload.org_id, - data: { - durationMs: json.data.durationMs, - costInCents: String(costInCents), - taskIdentifier: taskRun.taskIdentifier, - }, - }); - } catch (e) { - logger.error("Failed to report usage event, enqueing v3.reportUsage", { error: e }); - - await workerQueue.enqueue("v3.reportUsage", { - orgId: jwtResult.payload.org_id, - data: { - costInCents: String(costInCents), - }, - additionalData: { - durationMs: json.data.durationMs, - taskIdentifier: taskRun.taskIdentifier, - }, - }); - } - } - - return new Response(null, { - status: 200, - headers: { - "x-trigger-jwt": jwtResult.jwt, - }, - }); } diff --git a/apps/webapp/app/services/platform.v3.server.ts b/apps/webapp/app/services/platform.v3.server.ts index f7f77e651..feae06b34 100644 --- a/apps/webapp/app/services/platform.v3.server.ts +++ b/apps/webapp/app/services/platform.v3.server.ts @@ -181,6 +181,15 @@ export async function getUsageSeries(organizationId: string, params: UsageSeries } } +export async function reportComputeUsage(request: Request) { + const client = getClient(); + if (!client) return undefined; + return fetch(`${process.env.BILLING_API_URL}/api/v1/usage/ingest/compute`, { + method: "POST", + headers: request.headers, + body: await request.text(), + }); +} export async function projectCreated(organization: Organization, project: Project) { if (project.version === "V2" || !isCloud()) { await createEnvironment(organization, project, "STAGING"); diff --git a/apps/webapp/app/v3/openMeter.server.ts b/apps/webapp/app/v3/openMeter.server.ts deleted file mode 100644 index 7b89a6712..000000000 --- a/apps/webapp/app/v3/openMeter.server.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { randomUUID } from "node:crypto"; -import { env } from "~/env.server"; -import { logger } from "~/services/logger.server"; - -export type UsageEvent = { - source: string; - subject: string; - type: string; - id?: string; - time?: Date; - data?: Record; -}; - -export async function reportUsageEvent(event: UsageEvent) { - if (!env.USAGE_OPEN_METER_BASE_URL || !env.USAGE_OPEN_METER_API_KEY) { - return; - } - - const body = { - specversion: "1.0", - id: event.id ?? randomUUID(), - source: event.source, - type: event.type, - time: (event.time ?? new Date()).toISOString(), - subject: event.subject, - datacontenttype: "application/json", - data: event.data, - }; - - const url = `${env.USAGE_OPEN_METER_BASE_URL}/api/v1/events`; - - logger.debug("Reporting usage event to OpenMeter", { url, body }); - - const response = await fetch(url, { - method: "POST", - body: JSON.stringify(body), - headers: { - "Content-Type": "application/cloudevents+json", - Authorization: `Bearer ${env.USAGE_OPEN_METER_API_KEY}`, - Accept: "application/json", - }, - }); - - if (!response.ok) { - logger.error(`Failed to report usage event: ${response.status} ${response.statusText}`); - } -}