diff --git a/backend/imap-idle/index.mjs b/backend/imap-idle/index.mjs index 5860d67..86f2290 100644 --- a/backend/imap-idle/index.mjs +++ b/backend/imap-idle/index.mjs @@ -50,6 +50,33 @@ const RECONNECT_LOOP_DELAY_MS = 60 * 1000; const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }); +async function updateConnectionError(connId, errorText) { + await pool.query( + `UPDATE rebreak.mail_connections + SET last_connect_error = $1, last_connect_error_at = NOW() + WHERE id = $2`, + [errorText, connId], + ); +} + +async function clearConnectionError(connId) { + await pool.query( + `UPDATE rebreak.mail_connections + SET last_connect_error = NULL, last_connect_error_at = NULL + WHERE id = $1`, + [connId], + ); +} + +async function updateIdleHeartbeat(connId) { + await pool.query( + `UPDATE rebreak.mail_connections + SET last_idle_heartbeat_at = NOW() + WHERE id = $1`, + [connId], + ); +} + async function loadActiveConnections() { // DB-table heißt "mail_connections" + snake_case columns (Prisma @map). // Aliase auf camelCase damit der restliche Daemon-Code unverändert bleibt. @@ -106,8 +133,9 @@ function log(email, msg) { } function logError(email, msg, err) { - const errMsg = err?.message ?? String(err); - // Credentials tauchen nie in err.message auf (ImapFlow maskiert sie nicht, + // responseText enthält z.B. IMAP-Serverantwort bei Auth-Fehlern ("NO [AUTHENTICATIONFAILED]") + const errMsg = err?.responseText || err?.message || String(err); + // Credentials tauchen nie in err.responseText/message auf (ImapFlow maskiert sie nicht, // aber wir liefern pass nur an ImapFlow — nicht in eigenen log-calls). console.error(`[idle/${email}] ${msg}: ${errMsg}`); } @@ -185,6 +213,7 @@ async function runSession(conn) { await imap.connect(); log(conn.email, `connected (${conn.imapHost}:${conn.imapPort})`); attempt = 0; // Reset nach erfolgreicher Verbindung + await clearConnectionError(conn.id).catch(() => {}); // clear stale auth-error await imap.getMailboxLock("INBOX"); @@ -223,8 +252,7 @@ async function runSession(conn) { const noopTimer = setInterval(async () => { try { await imap.noop(); - // Optional: verbose-log für debugging — aktuell silent - // log(conn.email, "noop ok"); + await updateIdleHeartbeat(conn.id).catch(() => {}); // UI: "connection alive" } catch (err) { logError(conn.email, "noop failed — connection dead, force reconnect", err); imap.close(); @@ -250,6 +278,8 @@ async function runSession(conn) { } catch (err) { logError(conn.email, "connection error", err); + const errText = err?.responseText || err?.message || String(err); + await updateConnectionError(conn.id, errText).catch(() => {}); try { imap.close(); } catch { /* ignore */ } } diff --git a/backend/prisma/migrations/20260509_add_mail_connection_status_fields/migration.sql b/backend/prisma/migrations/20260509_add_mail_connection_status_fields/migration.sql new file mode 100644 index 0000000..6cbb40f --- /dev/null +++ b/backend/prisma/migrations/20260509_add_mail_connection_status_fields/migration.sql @@ -0,0 +1,8 @@ +-- Migration: add_mail_connection_status_fields +-- Adds error-tracking + IDLE heartbeat timestamp to mail_connections. +-- Deploy: pnpm prisma migrate deploy (on server) + +ALTER TABLE "rebreak"."mail_connections" + ADD COLUMN "last_connect_error" TEXT, + ADD COLUMN "last_connect_error_at" TIMESTAMP(3), + ADD COLUMN "last_idle_heartbeat_at" TIMESTAMP(3); diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 644b2af..9c1aa3e 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -480,6 +480,9 @@ model MailConnection { nextScanAt DateTime? @map("next_scan_at") emailsBlocked Int @default(0) @map("emails_blocked") emailsScanned Int @default(0) @map("emails_scanned") + lastConnectError String? @map("last_connect_error") + lastConnectErrorAt DateTime? @map("last_connect_error_at") + lastIdleHeartbeatAt DateTime? @map("last_idle_heartbeat_at") createdAt DateTime @default(now()) @map("created_at") blockedMails MailBlocked[] diff --git a/backend/server/api/mail/status.get.ts b/backend/server/api/mail/status.get.ts index 0a604d7..ec21f27 100644 --- a/backend/server/api/mail/status.get.ts +++ b/backend/server/api/mail/status.get.ts @@ -23,6 +23,9 @@ export default defineEventHandler(async (event) => { c.emailsScanned > 0 ? Math.round((c.emailsBlocked / c.emailsScanned) * 100) : 0, + lastConnectError: c.lastConnectError ?? null, + lastConnectErrorAt: c.lastConnectErrorAt?.toISOString() ?? null, + lastIdleHeartbeatAt: c.lastIdleHeartbeatAt?.toISOString() ?? null, })); const blocked7d = await getMailBlockedStats(user.id);