diff --git a/apps/web/.env.example b/apps/web/.env.example index b72b4c161..f68c5a062 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -47,11 +47,7 @@ SENTRY_AUTH_TOKEN='' UNKEY_API_ID= UNKEY_TOKEN= -# GCP Messaging Queue -GCP_PROJECT_ID= -GCP_LOCATION= -GCP_CLIENT_EMAIL= -GCP_PRIVATE_KEY= +# Cron CRON_SECRET= EXTERNAL_API_URL=http://server:3000 diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 24165b863..057577423 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -69,10 +69,6 @@ const nextConfig: NextConfig = { OPENSTATUS_MCP_SERVER_VERSION: readMcpServerVersion(), }, outputFileTracingIncludes: { - "/": [ - "./node_modules/.pnpm/@google-cloud/tasks/build/esm/src/**/*.json", - "./node_modules/@google-cloud/tasks/build/esm/src/**/*.js", - ], "/.well-known/agent-skills/index.json": [ "./public/.well-known/agent-skills/**/*.md", ], @@ -83,7 +79,6 @@ const nextConfig: NextConfig = { // barrel-optimize only when unaliased — the rewrite would bypass the nucleo alias optimizePackageImports: useNucleoIcons ? [] : ["@openstatus/icons"], }, - serverExternalPackages: ["@google-cloud/tasks"], expireTime: 180, // 3 minutes logging: { fetches: { diff --git a/apps/web/package.json b/apps/web/package.json index 8154683de..0824cfad0 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -15,7 +15,6 @@ "dependencies": { "@auth/core": "catalog:", "@auth/drizzle-adapter": "catalog:", - "@google-cloud/tasks": "catalog:", "@hookform/resolvers": "catalog:", "@libsql/client": "catalog:", "@openpanel/nextjs": "catalog:", diff --git a/apps/web/src/app/api/checker/cron/10m/route.ts b/apps/web/src/app/api/checker/cron/10m/route.ts deleted file mode 100644 index 09222f99b..000000000 --- a/apps/web/src/app/api/checker/cron/10m/route.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { NextRequest } from "next/server"; -import { NextResponse } from "next/server"; - -import { cron, isAuthorizedDomain } from "../_cron"; -import { runSentryCron } from "../_sentry"; - -export const runtime = "nodejs"; -// export const preferredRegion = ["auto"]; -export const dynamic = "force-dynamic"; -export const maxDuration = 300; -export const revalidate = 0; - -export async function GET(req: NextRequest) { - if (isAuthorizedDomain(req.url)) { - const { cronCompleted, cronFailed } = runSentryCron("10-m-cron"); - try { - await cron({ periodicity: "10m", req }); - await cronCompleted(); - } catch (_error) { - await cronFailed(); - } - } - return NextResponse.json({ success: true }); -} diff --git a/apps/web/src/app/api/checker/cron/1h/route.ts b/apps/web/src/app/api/checker/cron/1h/route.ts deleted file mode 100644 index f470d2d71..000000000 --- a/apps/web/src/app/api/checker/cron/1h/route.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { NextRequest } from "next/server"; -import { NextResponse } from "next/server"; - -import { cron, isAuthorizedDomain } from "../_cron"; -import { runSentryCron } from "../_sentry"; - -export const runtime = "nodejs"; -// export const preferredRegion = ["auto"]; -export const dynamic = "force-dynamic"; -export const maxDuration = 300; -export const revalidate = 0; - -export async function GET(req: NextRequest) { - if (isAuthorizedDomain(req.url)) { - const { cronCompleted, cronFailed } = runSentryCron("1-h-cron"); - try { - await cron({ periodicity: "1h", req }); - await cronCompleted(); - } catch (_error) { - await cronFailed(); - } - } - return NextResponse.json({ success: true }); -} diff --git a/apps/web/src/app/api/checker/cron/1m/route.ts b/apps/web/src/app/api/checker/cron/1m/route.ts deleted file mode 100644 index 017973c59..000000000 --- a/apps/web/src/app/api/checker/cron/1m/route.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { NextRequest } from "next/server"; -import { NextResponse } from "next/server"; - -import { cron, isAuthorizedDomain } from "../_cron"; -import { runSentryCron } from "../_sentry"; - -export const runtime = "nodejs"; -// export const preferredRegion = ["auto"]; -export const dynamic = "force-dynamic"; -export const maxDuration = 300; -export const revalidate = 0; - -export async function GET(req: NextRequest) { - if (isAuthorizedDomain(req.url)) { - const { cronCompleted, cronFailed } = runSentryCron("1-m-cron"); - try { - await cron({ periodicity: "1m", req }); - await cronCompleted(); - } catch (_error) { - await cronFailed(); - } - } - return NextResponse.json({ success: true }); -} diff --git a/apps/web/src/app/api/checker/cron/30m/route.ts b/apps/web/src/app/api/checker/cron/30m/route.ts deleted file mode 100644 index ee763b1c0..000000000 --- a/apps/web/src/app/api/checker/cron/30m/route.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { NextRequest } from "next/server"; -import { NextResponse } from "next/server"; - -import { cron, isAuthorizedDomain } from "../_cron"; -import { runSentryCron } from "../_sentry"; - -export const runtime = "nodejs"; -// export const preferredRegion = ["auto"]; -export const dynamic = "force-dynamic"; -export const maxDuration = 300; -export const revalidate = 0; - -export async function GET(req: NextRequest) { - if (isAuthorizedDomain(req.url)) { - const { cronCompleted, cronFailed } = runSentryCron("30-m-cron"); - try { - await cron({ periodicity: "30m", req }); - await cronCompleted(); - } catch (_error) { - await cronFailed(); - } - } - return NextResponse.json({ success: true }); -} diff --git a/apps/web/src/app/api/checker/cron/30s/route.ts b/apps/web/src/app/api/checker/cron/30s/route.ts deleted file mode 100644 index afcf1c977..000000000 --- a/apps/web/src/app/api/checker/cron/30s/route.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { NextRequest } from "next/server"; -import { NextResponse } from "next/server"; - -import { cron, isAuthorizedDomain } from "../_cron"; -import { runSentryCron } from "../_sentry"; - -export const runtime = "nodejs"; -// export const preferredRegion = ["auto"]; -export const dynamic = "force-dynamic"; -export const maxDuration = 300; -export const revalidate = 0; - -export async function GET(req: NextRequest) { - if (isAuthorizedDomain(req.url)) { - const { cronCompleted, cronFailed } = runSentryCron("30-s-cron"); - try { - await cron({ periodicity: "30s", req }); - await cronCompleted(); - } catch (_error) { - await cronFailed(); - } - } - return NextResponse.json({ success: true }); -} diff --git a/apps/web/src/app/api/checker/cron/5m/route.ts b/apps/web/src/app/api/checker/cron/5m/route.ts deleted file mode 100644 index 000bf79e0..000000000 --- a/apps/web/src/app/api/checker/cron/5m/route.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { NextRequest } from "next/server"; -import { NextResponse } from "next/server"; - -import { cron, isAuthorizedDomain } from "../_cron"; -import { runSentryCron } from "../_sentry"; - -export const runtime = "nodejs"; -// export const preferredRegion = ["auto"]; -export const dynamic = "force-dynamic"; -export const maxDuration = 300; -export const revalidate = 0; - -export async function GET(req: NextRequest) { - if (isAuthorizedDomain(req.url)) { - const { cronCompleted, cronFailed } = runSentryCron("5-m-cron"); - try { - await cron({ periodicity: "5m", req }); - await cronCompleted(); - } catch (_error) { - await cronFailed(); - } - } - return NextResponse.json({ success: true }); -} diff --git a/apps/web/src/app/api/checker/cron/_cron.ts b/apps/web/src/app/api/checker/cron/_cron.ts deleted file mode 100644 index 96e7ab95e..000000000 --- a/apps/web/src/app/api/checker/cron/_cron.ts +++ /dev/null @@ -1,294 +0,0 @@ -import { CloudTasksClient, type protos } from "@google-cloud/tasks"; -import { and, db, eq, gte, isNotNull, lte, notInArray } from "@openstatus/db"; -import type { MonitorStatus } from "@openstatus/db/src/schema"; -import { - maintenance, - monitor, - monitorStatusTable, - selectMonitorSchema, - selectMonitorStatusSchema, -} from "@openstatus/db/src/schema"; -import type { Region } from "@openstatus/db/src/schema/constants"; -import { - maintenancesToPageComponents, - pageComponent, -} from "@openstatus/db/src/schema/page_components"; -import { regionDict } from "@openstatus/regions"; -import { - type httpPayloadSchema, - type tpcPayloadSchema, - transformHeaders, -} from "@openstatus/utils"; -import type { NextRequest } from "next/server"; -import { z } from "zod"; - -import { env } from "../../../../env"; - -const periodicityAvailable = selectMonitorSchema.pick({ periodicity: true }); - -// FIXME: do coerce in zod instead - -const DEFAULT_URL = process.env.VERCEL_URL - ? `https://${process.env.VERCEL_URL}` - : "http://localhost:3000"; - -// We can't secure cron endpoint by vercel thus we should make sure they are called by the generated url -export const isAuthorizedDomain = (url: string) => { - return url.includes(DEFAULT_URL); -}; - -export const cron = async ({ - periodicity, - // oxlint-disable-next-line eslint/no-unused-vars - req, -}: z.infer & { req: NextRequest }) => { - const client = new CloudTasksClient({ - projectId: env.GCP_PROJECT_ID, - credentials: { - client_email: process.env.GCP_CLIENT_EMAIL, - private_key: env.GCP_PRIVATE_KEY.replaceAll("\\n", "\n"), - }, - }); - const parent = client.queuePath( - env.GCP_PROJECT_ID, - env.GCP_LOCATION, - periodicity, - ); - - const timestamp = Date.now(); - - const currentMaintenance = db - .select({ id: maintenance.id }) - .from(maintenance) - .where( - and(lte(maintenance.from, new Date()), gte(maintenance.to, new Date())), - ) - .as("currentMaintenance"); - - const currentMaintenanceMonitors = db - .select({ id: pageComponent.monitorId }) - .from(maintenancesToPageComponents) - .innerJoin( - currentMaintenance, - eq(maintenancesToPageComponents.maintenanceId, currentMaintenance.id), - ) - .innerJoin( - pageComponent, - eq(maintenancesToPageComponents.pageComponentId, pageComponent.id), - ) - .where(isNotNull(pageComponent.monitorId)); - - const result = await db - .select() - .from(monitor) - .where( - and( - eq(monitor.periodicity, periodicity), - eq(monitor.active, true), - notInArray(monitor.id, currentMaintenanceMonitors), - ), - ) - .all(); - - console.log(`Start cron for ${periodicity}`); - - const monitors = z.array(selectMonitorSchema).safeParse(result); - const allResult = []; - if (!monitors.success) { - console.error( - `Error while fetching the monitors ${monitors.error.issues.map((issue) => issue.message).join(", ")}`, - ); - throw new Error("Error while fetching the monitors"); - } - - for (const row of monitors.data) { - // const selectedRegions = row.regions.length > 0 ? row.regions : ["ams"]; - - const result = await db - .select() - .from(monitorStatusTable) - .where(eq(monitorStatusTable.monitorId, row.id)) - .all(); - const monitorStatus = z.array(selectMonitorStatusSchema).safeParse(result); - if (!monitorStatus.success) { - console.error( - `Error while fetching the monitor status ${monitorStatus.error.issues.map((issue) => issue.message).join(", ")}`, - ); - continue; - } - - for (const region of row.regions) { - const status = - monitorStatus.data.find((m) => region === m.region)?.status || "active"; - - const r = regionDict[region as keyof typeof regionDict]; - - if (!r) { - console.error(`Invalid region ${region}`); - continue; - } - if (r.deprecated) { - // Let's uncomment this when we are ready to remove deprecated regions - // We should not use deprecated regions anymore - console.error(`Deprecated region ${region}`); - continue; - } - const response = createCronTask({ - row, - timestamp, - client, - parent, - status, - region, - }); - allResult.push(response); - if (periodicity === "30s") { - // we schedule another task in 30s - const scheduledAt = timestamp + 30 * 1000; - const response = createCronTask({ - row, - timestamp: scheduledAt, - client, - parent, - status, - region, - }); - allResult.push(response); - } - } - } - - const allRequests = await Promise.allSettled(allResult); - - const success = allRequests.filter((r) => r.status === "fulfilled").length; - const failed = allRequests.filter((r) => r.status === "rejected").length; - - console.log( - `End cron for ${periodicity} with ${allResult.length} jobs with ${success} success and ${failed} failed`, - ); -}; -// timestamp needs to be in ms -const createCronTask = async ({ - row, - timestamp, - client, - parent, - status, - region, -}: { - row: z.infer; - timestamp: number; - client: CloudTasksClient; - parent: string; - status: MonitorStatus; - region: Region; -}) => { - let payload: - | z.infer - | z.infer - | null = null; - - // - if (row.jobType === "http") { - payload = { - workspaceId: String(row.workspaceId), - monitorId: String(row.id), - url: row.url, - method: row.method || "GET", - cronTimestamp: timestamp, - body: row.body, - headers: row.headers, - status: status, - assertions: row.assertions ? JSON.parse(row.assertions) : null, - degradedAfter: row.degradedAfter, - timeout: row.timeout, - trigger: "cron", - otelConfig: row.otelEndpoint - ? { - endpoint: row.otelEndpoint, - headers: transformHeaders(row.otelHeaders), - } - : undefined, - retry: row.retry || 3, - followRedirects: row.followRedirects || true, - }; - } - if (row.jobType === "tcp") { - payload = { - workspaceId: String(row.workspaceId), - monitorId: String(row.id), - uri: row.url, - status: status, - assertions: row.assertions ? JSON.parse(row.assertions) : null, - cronTimestamp: timestamp, - degradedAfter: row.degradedAfter, - timeout: row.timeout, - trigger: "cron", - retry: row.retry || 3, - otelConfig: row.otelEndpoint - ? { - endpoint: row.otelEndpoint, - headers: transformHeaders(row.otelHeaders), - } - : undefined, - }; - } - - if (!payload) { - throw new Error("Invalid jobType"); - } - const regionInfo = regionDict[region]; - let regionHeader = {}; - if (regionInfo.provider === "fly") { - regionHeader = { "fly-prefer-region": region }; - } - if (regionInfo.provider === "koyeb") { - regionHeader = { "X-KOYEB-REGION-OVERRIDE": region.replace("koyeb_", "") }; - } - if (regionInfo.provider === "railway") { - regionHeader = { "railway-region": region.replace("railway_", "") }; - } - const newTask: protos.google.cloud.tasks.v2beta3.ITask = { - httpRequest: { - headers: { - "Content-Type": "application/json", // Set content type to ensure compatibility your application's request parsing - ...regionHeader, - Authorization: `Basic ${env.CRON_SECRET}`, - }, - httpMethod: "POST", - url: generateUrl({ row, region }), - body: Buffer.from(JSON.stringify(payload)).toString("base64"), - }, - scheduleTime: { - seconds: timestamp / 1000, - }, - // The checker answers only after its whole retry loop, which a 5m timeout - // can stretch well past Cloud Tasks' 10m default; 30m is the max allowed. - dispatchDeadline: { seconds: 30 * 60 }, - }; - - const request = { parent: parent, task: newTask }; - return client.createTask(request); -}; - -function generateUrl({ - row, - region, -}: { - row: z.infer; - region: Region; -}) { - const regionInfo = regionDict[region]; - - switch (regionInfo.provider) { - case "fly": - return `https://openstatus-checker.fly.dev/checker/${row.jobType}?monitor_id=${row.id}`; - case "koyeb": - return `https://openstatus-checker.koyeb.app/checker/${row.jobType}?monitor_id=${row.id}`; - case "railway": - return `https://railway-proxy-production-9cb1.up.railway.app/checker/${row.jobType}?monitor_id=${row.id}`; - - default: - throw new Error("Invalid jobType"); - } -} diff --git a/apps/web/src/app/api/checker/cron/_sentry.ts b/apps/web/src/app/api/checker/cron/_sentry.ts deleted file mode 100644 index 67b992ffc..000000000 --- a/apps/web/src/app/api/checker/cron/_sentry.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Props: https://github.com/getsentry/sentry-javascript/issues/9335#issuecomment-1779057528 -import { captureCheckIn, flush } from "@sentry/nextjs"; - -export function runSentryCron(monitorSlug: string) { - // 🟡 Notify Sentry your job is running: - const checkInId = captureCheckIn({ - monitorSlug, - status: "in_progress", - }); - return { - cronCompleted: async () => { - // 🟢 Notify Sentry your job has completed successfully: - captureCheckIn({ - checkInId, - monitorSlug, - status: "ok", - }); - return flush(); - }, - cronFailed: async () => { - // 🔴 Notify Sentry your job has failed: - captureCheckIn({ - checkInId, - monitorSlug, - status: "error", - }); - return flush(); - }, - }; -} diff --git a/apps/web/src/env.ts b/apps/web/src/env.ts index a01428b25..e8e9a09d4 100644 --- a/apps/web/src/env.ts +++ b/apps/web/src/env.ts @@ -15,10 +15,6 @@ export const env = createEnv({ STRIPE_WEBHOOK_SECRET_KEY: z.string(), UNKEY_TOKEN: z.string(), UNKEY_API_ID: z.string(), - GCP_PROJECT_ID: z.string(), - GCP_LOCATION: z.string(), - GCP_CLIENT_EMAIL: z.string(), - GCP_PRIVATE_KEY: z.string(), CRON_SECRET: z.string(), PAGERDUTY_APP_ID: z.string().optional(), SLACK_FEEDBACK_WEBHOOK_URL: z.string().optional(), @@ -46,10 +42,6 @@ export const env = createEnv({ NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN, UNKEY_TOKEN: process.env.UNKEY_TOKEN, UNKEY_API_ID: process.env.UNKEY_API_ID, - GCP_PROJECT_ID: process.env.GCP_PROJECT_ID, - GCP_LOCATION: process.env.GCP_LOCATION, - GCP_CLIENT_EMAIL: process.env.GCP_CLIENT_EMAIL, - GCP_PRIVATE_KEY: process.env.GCP_PRIVATE_KEY, CRON_SECRET: process.env.CRON_SECRET, PAGERDUTY_APP_ID: process.env.PAGERDUTY_APP_ID, SLACK_FEEDBACK_WEBHOOK_URL: process.env.SLACK_FEEDBACK_WEBHOOK_URL, diff --git a/apps/workflows/src/cron/checker.ts b/apps/workflows/src/cron/checker.ts index 9ed195be1..7320fb3c4 100644 --- a/apps/workflows/src/cron/checker.ts +++ b/apps/workflows/src/cron/checker.ts @@ -1,6 +1,4 @@ import { PubSub, type Topic } from "@google-cloud/pubsub"; -import { CloudTasksClient } from "@google-cloud/tasks"; -import type { google } from "@google-cloud/tasks/build/protos"; import { getLogger } from "@logtape/logtape"; import { and, @@ -18,13 +16,11 @@ import { selectMonitorSchema, selectMonitorStatusSchema, } from "@openstatus/db/src/schema"; -import type { Region } from "@openstatus/db/src/schema/constants"; import { maintenancesToPageComponents, pageComponent, } from "@openstatus/db/src/schema/page_components"; import { regionDict } from "@openstatus/regions"; -import { Effect, Result, Schedule } from "effect"; import { z } from "zod"; import { env } from "../env"; @@ -33,7 +29,6 @@ import { type CheckerPublisher, type Periodicity, type TaskInput, - buildCheckerPayload, publishCheckerTasks, } from "./checker-payload"; @@ -44,7 +39,7 @@ export const isAuthorizedDomain = (url: string) => { const logger = getLogger("workflow"); /** - * Check if GCP Cloud Tasks is properly configured. + * Check if GCP Pub/Sub is properly configured. * Returns false if credentials are missing or set to placeholder values. */ function isGcpConfigured(): boolean { @@ -65,9 +60,8 @@ function isGcpConfigured(): boolean { ); } -// Only initialize CloudTasksClient if GCP credentials are properly configured -// This allows self-hosted deployments to run without GCP Cloud Tasks -let client: CloudTasksClient | null = null; +// Only initialize the Pub/Sub publisher if GCP credentials are properly configured +// This allows self-hosted deployments to run without GCP Pub/Sub let publisher: CheckerPublisher | null = null; if (isGcpConfigured()) { @@ -78,7 +72,6 @@ if (isGcpConfigured()) { private_key: env().GCP_PRIVATE_KEY.replaceAll("\\n", "\n"), }, }; - client = new CloudTasksClient(options); publisher = createPublisher(new PubSub(options)); } @@ -102,8 +95,8 @@ export async function sendCheckerTasks( periodicity: Periodicity, ): Promise<{ success: number; failed: number }> { // If GCP is not configured, skip task creation - if (!client || !publisher) { - logger.warn("GCP Cloud Tasks not configured - skipping checker tasks", { + if (!publisher) { + logger.warn("GCP Pub/Sub not configured - skipping checker tasks", { periodicity, }); return { success: 0, failed: 0 }; @@ -219,15 +212,14 @@ export async function sendCheckerTasks( } } - const transport = env().CHECKER_TRANSPORT; - const { success, failed } = - transport === "pubsub" - ? await publishToPubSub(taskInputs, periodicity, publisher) - : await createCloudTasks(taskInputs, periodicity, client); + const { success, failed } = await publishToPubSub( + taskInputs, + periodicity, + publisher, + ); logger.info("Completed cron job", { periodicity, - transport, total_tasks: taskInputs.length, success_count: success, failed_count: failed, @@ -236,7 +228,6 @@ export async function sendCheckerTasks( if (failed > 0) { logger.error("Cron job had failures", { periodicity, - transport, failed_count: failed, success_count: success, }); @@ -260,121 +251,3 @@ async function publishToPubSub( } return result; } - -async function createCloudTasks( - taskInputs: TaskInput[], - periodicity: Periodicity, - client: CloudTasksClient, -) { - const parent = client.queuePath( - env().GCP_PROJECT_ID, - env().GCP_LOCATION, - periodicity, - ); - - const results = await Effect.runPromise( - Effect.forEach( - taskInputs, - (input) => - Effect.tryPromise({ - try: () => createCronTask(input, parent, client), - catch: (err) => { - if (err instanceof Error && "code" in err && err.code === 6) { - return "ALREADY_EXISTS" as const; - } - return new Error( - `Failed creating task for monitor ${input.row.id} in region ${input.region}`, - ); - }, - }).pipe( - Effect.catchIf( - (err): err is "ALREADY_EXISTS" => err === "ALREADY_EXISTS", - () => Effect.void, - ), - Effect.retry({ - times: 3, - schedule: Schedule.exponential("1000 millis"), - }), - Effect.result, - ), - { concurrency: 100 }, - ), - ); - - for (const result of results) { - if (Result.isFailure(result)) { - logger.error("Task creation failed after retries", { - error_message: result.failure.message, - }); - } - } - - return { - success: results.filter(Result.isSuccess).length, - failed: results.filter(Result.isFailure).length, - }; -} - -const createCronTask = async ( - { row, timestamp, status, region }: TaskInput, - parent: string, - client: CloudTasksClient, -) => { - const payload = buildCheckerPayload({ row, timestamp, status }); - const regionInfo = regionDict[region]; - let regionHeader = {}; - if (regionInfo.provider === "fly") { - regionHeader = { "fly-prefer-region": region }; - } - if (regionInfo.provider === "koyeb") { - regionHeader = { "X-KOYEB-REGION-OVERRIDE": region.replace("koyeb_", "") }; - } - if (regionInfo.provider === "railway") { - regionHeader = { "railway-region": region.replace("railway_", "") }; - } - const taskName = `${parent}/tasks/monitor-${row.id}-${region}-${timestamp}`; - const newTask: google.cloud.tasks.v2beta3.ITask = { - name: taskName, - httpRequest: { - headers: { - "Content-Type": "application/json", // Set content type to ensure compatibility your application's request parsing - ...regionHeader, - Authorization: `Basic ${env().CRON_SECRET}`, - }, - httpMethod: "POST", - url: generateUrl({ row, region }), - body: Buffer.from(JSON.stringify(payload)).toString("base64"), - }, - scheduleTime: { - seconds: timestamp / 1000, - }, - // The checker answers only after its whole retry loop, which a 5m timeout - // can stretch well past Cloud Tasks' 10m default; 30m is the max allowed. - dispatchDeadline: { seconds: 30 * 60 }, - }; - - const request = { parent: parent, task: newTask }; - return client.createTask(request); -}; - -function generateUrl({ - row, - region, -}: { - row: z.infer; - region: Region; -}) { - const regionInfo = regionDict[region]; - - switch (regionInfo.provider) { - case "fly": - return `https://openstatus-checker.fly.dev/checker/${row.jobType}?monitor_id=${row.id}`; - case "koyeb": - return `https://openstatus-checker.koyeb.app/checker/${row.jobType}?monitor_id=${row.id}`; - case "railway": - return `https://railway-proxy-production-9cb1.up.railway.app/checker/${row.jobType}?monitor_id=${row.id}`; - - default: - throw new Error("Invalid jobType"); - } -} diff --git a/apps/workflows/src/env.ts b/apps/workflows/src/env.ts index 638169314..f245135d2 100644 --- a/apps/workflows/src/env.ts +++ b/apps/workflows/src/env.ts @@ -9,9 +9,6 @@ export const env = () => GCP_CLIENT_EMAIL: z.string().prefault(""), GCP_PRIVATE_KEY: z.string().prefault(""), GCP_LOCATION: z.string().prefault("europe-west1"), - CHECKER_TRANSPORT: z - .enum(["cloudtasks", "pubsub"]) - .prefault("cloudtasks"), CRON_SECRET: z.string().prefault(""), SITE_URL: z.string().prefault("http://localhost:3000"), DATABASE_URL: z.string().prefault("http://localhost:8080"), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1436656fb..7557c9616 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1544,9 +1544,6 @@ importers: '@auth/drizzle-adapter': specifier: 'catalog:' version: 1.11.3 - '@google-cloud/tasks': - specifier: 'catalog:' - version: 7.1.1(supports-color@8.1.1) '@hookform/resolvers': specifier: 'catalog:' version: 5.9.1(@standard-schema/spec@1.1.0)(ajv-formats@2.1.1(ajv@8.20.0))(ajv@8.20.0)(react-hook-form@7.88.0(react@19.3.0))(zod@4.6.5)