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
This commit is contained in:
saravanakumardb1 2026-04-04 20:56:09 -07:00
parent 89e200fa9f
commit 05594a334f
2 changed files with 54 additions and 0 deletions

View File

@ -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);

View File

@ -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<JobResult> {
return { success: true, message: `Found ${warned} licenses expiring soon`, metrics: { warned } };
}
async function devintelliDailySync(ctx: JobContext): Promise<JobResult> {
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<string, unknown>;
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 };
}
}