Sidecar/queue runner no longer needs job-specific context. API server resolves account details, IMAP auth, and user email at enqueue time — all persisted in the job file. Handler reads directly from job meta. Removed "Load account" step. Sync step auto-reconnects up to 10 times when Gmail drops the connection, resuming from saved UIDs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
88 lines
2.9 KiB
TypeScript
88 lines
2.9 KiB
TypeScript
import { getAllSyncedAccounts, getUserById, getUserIntegration } from 'officerdb';
|
|
import * as queueRunner from './queue-runner';
|
|
|
|
const INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
|
|
|
|
let timer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
async function tick() {
|
|
try {
|
|
const accounts = await getAllSyncedAccounts();
|
|
if (accounts.length === 0) return;
|
|
|
|
const allJobs = await queueRunner.listAllJobs();
|
|
const activeEmailSyncIds = new Set(
|
|
allJobs
|
|
.filter((j) => j.type === 'email-sync' && (j.status === 'queued' || j.status === 'running'))
|
|
.map((j) => (j.meta as Record<string, unknown> | undefined)?.emailAccountId),
|
|
);
|
|
|
|
for (const account of accounts) {
|
|
if (activeEmailSyncIds.has(account.id)) continue;
|
|
|
|
const user = await getUserById(account.userId);
|
|
if (!user) continue;
|
|
|
|
// Resolve IMAP auth
|
|
const imapAuth: Record<string, unknown> = { user: account.email };
|
|
if (account.authType === 'oauth') {
|
|
const integration = await getUserIntegration(account.userId, 'google');
|
|
const config = integration?.config as Record<string, unknown> | undefined;
|
|
const accessToken = config?.accessToken as string | undefined;
|
|
if (!accessToken) {
|
|
console.log(`[email-cron] Skipping ${account.email}: no OAuth access token`);
|
|
continue;
|
|
}
|
|
imapAuth.accessToken = accessToken;
|
|
} else {
|
|
const creds = account.credentials as Record<string, unknown>;
|
|
imapAuth.pass = creds.password;
|
|
}
|
|
|
|
try {
|
|
await queueRunner.enqueue({
|
|
lane: 'email',
|
|
type: 'email-sync',
|
|
userId: user.email,
|
|
meta: {
|
|
emailAccountId: account.id,
|
|
userEmail: user.email,
|
|
account: {
|
|
id: account.id,
|
|
userId: account.userId,
|
|
email: account.email,
|
|
imapHost: account.imapHost,
|
|
imapPort: account.imapPort,
|
|
imapSecure: account.imapSecure,
|
|
provider: account.provider,
|
|
authType: account.authType,
|
|
credentials: account.credentials,
|
|
},
|
|
imapAuth,
|
|
},
|
|
});
|
|
console.log(`[email-cron] Enqueued incremental sync for ${account.email}`);
|
|
} catch (err) {
|
|
console.error(`[email-cron] Failed to enqueue sync for ${account.email}:`, err instanceof Error ? err.message : err);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('[email-cron] Error:', err instanceof Error ? err.message : err);
|
|
}
|
|
}
|
|
|
|
export function initEmailCron() {
|
|
if (timer) return;
|
|
console.log(`[email-cron] Starting email sync cron (every ${INTERVAL_MS / 60_000} min)`);
|
|
timer = setInterval(tick, INTERVAL_MS);
|
|
// Run first tick after a short delay to let the queue initialize
|
|
setTimeout(tick, 30_000);
|
|
}
|
|
|
|
export function stopEmailCron() {
|
|
if (timer) {
|
|
clearInterval(timer);
|
|
timer = null;
|
|
}
|
|
}
|