From 05594a334fee0010e1ff7647422c2fa768dea49d Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Sat, 4 Apr 2026 20:56:09 -0700 Subject: [PATCH] feat(jobs): register devintelli-daily-sync cron job (0 6 * * *) - Add devintelli-daily-sync handler: POST to DevIntelli backend /api/sync/daily - Uses x-internal-key header for service-to-service auth - Add DEVINTELLI_BACKEND_URL + DEVINTELLI_INTERNAL_API_KEY env vars - Cron: 0 6 * * * (6am UTC daily), timeout: 5 min - Returns triggered/skipped/totalConnections metrics from DevIntelli response --- services/platform-service/src/lib/config.ts | 3 ++ .../src/modules/jobs/built-in-jobs.ts | 51 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/services/platform-service/src/lib/config.ts b/services/platform-service/src/lib/config.ts index 507e6c0a..fd8d3f7f 100644 --- a/services/platform-service/src/lib/config.ts +++ b/services/platform-service/src/lib/config.ts @@ -68,6 +68,9 @@ const envSchema = z.object({ EVENT_BUS_FILE: z.string().default('.data/platform-events.json'), EVENT_BUS_POLL_MS: z.coerce.number().default(100), EVENT_BUS_LEASE_MS: z.coerce.number().default(30_000), + // ── Cross-Service: DevIntelli ── + DEVINTELLI_BACKEND_URL: z.string().default('http://localhost:4021'), + DEVINTELLI_INTERNAL_API_KEY: z.string().default('dev-internal-key-do-not-use-in-prod'), }); export const config = envSchema.parse(process.env); diff --git a/services/platform-service/src/modules/jobs/built-in-jobs.ts b/services/platform-service/src/modules/jobs/built-in-jobs.ts index 3aa1c483..2b9598cb 100644 --- a/services/platform-service/src/modules/jobs/built-in-jobs.ts +++ b/services/platform-service/src/modules/jobs/built-in-jobs.ts @@ -1,4 +1,5 @@ import { getCollection } from '../../lib/datastore.js'; +import { config } from '../../lib/config.js'; import { registerJob } from './registry.js'; import type { JobContext, JobResult } from './types.js'; import type { SessionDoc } from '../sessions/types.js'; @@ -20,6 +21,7 @@ export function registerBuiltInJobs(): void { registerJob('telemetry-ttl-sweep', telemetryTtlSweep); registerJob('waitlist-reminder', waitlistReminder); registerJob('license-expiry-check', licenseExpiryCheck); + registerJob('devintelli-daily-sync', devintelliDailySync); } /** @@ -62,6 +64,12 @@ export const BUILT_IN_JOB_DEFAULTS = [ description: 'Warn users with expiring licenses (8am UTC)', timeoutMs: 120_000, }, + { + name: 'devintelli-daily-sync', + cron: '0 6 * * *', + description: 'DevIntelli daily GitHub sync (6am UTC)', + timeoutMs: 300_000, + }, ] as const; // ── Job Implementations ────────────────────────────────────── @@ -248,3 +256,46 @@ async function licenseExpiryCheck(ctx: JobContext): Promise { return { success: true, message: `Found ${warned} licenses expiring soon`, metrics: { warned } }; } + +async function devintelliDailySync(ctx: JobContext): Promise { + ctx.log.info({ jobName: ctx.jobName }, '[jobs] Running DevIntelli daily GitHub sync'); + + const baseUrl = config.DEVINTELLI_BACKEND_URL; + const apiKey = config.DEVINTELLI_INTERNAL_API_KEY; + const url = `${baseUrl}/api/sync/daily`; + + try { + const res = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-internal-key': apiKey, + }, + signal: AbortSignal.timeout(240_000), + }); + + if (!res.ok) { + const body = await res.text().catch(() => ''); + return { + success: false, + message: `DevIntelli backend returned ${res.status}: ${body.slice(0, 200)}`, + metrics: { statusCode: res.status }, + }; + } + + const data = (await res.json()) as Record; + return { + success: true, + message: `Triggered ${data.triggered ?? 0} syncs, skipped ${data.skipped ?? 0}`, + metrics: { + totalConnections: data.totalConnections, + triggered: data.triggered, + skipped: data.skipped, + }, + }; + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + ctx.log.error({ err, url }, '[jobs] DevIntelli daily sync failed'); + return { success: false, message: msg }; + } +}