bind the email folder filter instead of interpolating it
/email/messages and /email/stats built `labels LIKE '%${folder}%'` by string
interpolation, and `folder` comes straight off the query string. Five statements across
the two handlers were exposed. The fragment is bound now, and it carries its parameters
with it because each handler builds several statements from the same fragment and has
to spread them in order.
Checked against an in-memory table: inbox/INBOX/SENT/all return exactly what they
returned before, and `x' OR 1=1 --` now matches nothing instead of being SQL.
Also: page and limit reached the bindings as NaN for any non-numeric value, so a
mistyped query param was a 500. They fall back to their defaults now.
And deleted src/servers/sidecar/email-cron.ts — 92 lines imported by nothing. The live
cron is sidecar/email/email-cron.ts; this was an older copy that still reached into
queue-runner and google-auth directly, so leaving it there invites someone to fix the
wrong file.
This is the first commit on the email branch; the placement problems (the whole mail
store, both syncs, and the resync coalescing that cannot work across processes) are
untouched and much larger — see SIDECAR_WORK_LOG.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,92 +0,0 @@
|
||||
import { getAllSyncedAccounts, getUserById } from 'officerdb';
|
||||
import { getValidGoogleAccessToken } from '../api/integrations/google-auth';
|
||||
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') {
|
||||
let accessToken: string | null = null;
|
||||
try {
|
||||
accessToken = await getValidGoogleAccessToken(account.userId);
|
||||
} catch (err) {
|
||||
console.log(`[email-cron] Skipping ${account.email}: token refresh failed —`, err instanceof Error ? err.message : err);
|
||||
continue;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user