import { usePrisma } from "../utils/prisma"; export async function getMailConnections(userId: string) { const db = usePrisma(); // isActive=true UND nicht pausiert (pausedAt=null) — pausierte werden vom Cron ausgelassen return db.mailConnection.findMany({ where: { userId, isActive: true, pausedAt: null }, orderBy: { createdAt: "asc" }, }); } /** Alle Verbindungen eines Users inkl. pausierten — für Status-Anzeige im Frontend. */ export async function getAllMailConnections(userId: string) { const db = usePrisma(); return db.mailConnection.findMany({ where: { userId }, orderBy: { createdAt: "asc" }, select: { id: true, email: true, title: true, provider: true, providerName: true, imapHost: true, authMethod: true, consentAt: true, isActive: true, pausedAt: true, pausedReason: true, scanInterval: true, lastScannedAt: true, nextScanAt: true, emailsBlocked: true, emailsScanned: true, lastConnectError: true, createdAt: true, }, }); } export async function getAllActiveMailUserIds() { const db = usePrisma(); const rows = await db.mailConnection.findMany({ where: { isActive: true, nextScanAt: { lte: new Date() } }, select: { userId: true }, distinct: ["userId"], }); return rows.map((r) => r.userId); } export async function countMailConnections(userId: string) { const db = usePrisma(); // Nur aktive + nicht-pausierte Verbindungen zählen gegen das Limit return db.mailConnection.count({ where: { userId, isActive: true, pausedAt: null } }); } export async function upsertMailConnection(data: { userId: string; email: string; provider: string; providerName: string; imapHost: string; imapPort: number; passwordEncrypted: string; rejectUnauthorized?: boolean; useStarttls?: boolean; }) { const db = usePrisma(); return db.mailConnection.upsert({ where: { userId_email: { userId: data.userId, email: data.email } }, create: { ...data, isActive: true, rejectUnauthorized: data.rejectUnauthorized ?? true, useStarttls: data.useStarttls ?? false, }, update: { providerName: data.providerName, imapHost: data.imapHost, imapPort: data.imapPort, passwordEncrypted: data.passwordEncrypted, rejectUnauthorized: data.rejectUnauthorized ?? true, useStarttls: data.useStarttls ?? false, isActive: true, // Bei Re-Connect (z.B. neues App-Passwort): alte Error-Spuren clearen, // damit UI sofort wieder "Live" zeigt — IDLE-daemon übernimmt. lastConnectError: null, lastConnectErrorAt: null, }, }); } export async function deleteMailConnection( userId: string, connectionId: string, ) { const db = usePrisma(); return db.mailConnection.deleteMany({ where: { id: connectionId, userId }, }); } export async function deleteAllMailConnections(userId: string) { const db = usePrisma(); return db.mailConnection.deleteMany({ where: { userId } }); } export async function updateMailConnectionInterval( userId: string, connectionId: string, interval: number, ) { const db = usePrisma(); return db.mailConnection.updateMany({ where: { id: connectionId, userId }, data: { scanInterval: interval }, }); } export async function updateMailConnectionScanStats( connectionId: string, scanned: number, blocked: number, currentBlocked: number, currentScanned: number, scanIntervalHours: number, ) { const db = usePrisma(); return db.mailConnection.update({ where: { id: connectionId }, data: { lastScannedAt: new Date(), emailsBlocked: currentBlocked + blocked, emailsScanned: currentScanned + scanned, nextScanAt: new Date(Date.now() + scanIntervalHours * 3_600_000), }, }); } export async function getMailBlockedStats(userId: string) { const db = usePrisma(); const since7d = new Date(Date.now() - 7 * 86_400_000); return db.mailBlocked.findMany({ where: { userId, createdAt: { gte: since7d } }, select: { createdAt: true }, }); } export async function isMailAlreadyBlocked( gmailMessageId: string, userId: string, ) { const db = usePrisma(); const existing = await db.mailBlocked.findFirst({ where: { gmailMessageId, userId }, select: { id: true }, }); return !!existing; } export async function getAlreadyBlockedUidSet( uids: string[], userId: string, ): Promise> { if (uids.length === 0) return new Set(); const db = usePrisma(); const existing = await db.mailBlocked.findMany({ where: { gmailMessageId: { in: uids }, userId }, select: { gmailMessageId: true }, }); return new Set(existing.map((e) => e.gmailMessageId)); } export async function insertMailBlocked( entries: { userId: string; connectionId: string; gmailMessageId: string; senderEmail: string; senderName: string | null; subject: string; receivedAt: Date; action: string; }[], ) { if (entries.length === 0) return; const db = usePrisma(); await db.mailBlocked.createMany({ data: entries, skipDuplicates: true }); } /** * Gibt alle MailConnections eines Users zurück bei denen consent_at noch NULL ist. * Wird vom pending-consent.get.ts Endpoint für den Re-Consent-Modal-Trigger genutzt. */ export async function getPendingConsentConnections( userId: string, ): Promise<{ id: string; email: string }[]> { const db = usePrisma(); return db.mailConnection.findMany({ where: { userId, consentAt: null }, select: { id: true, email: true }, orderBy: { createdAt: "asc" }, }); } export async function getImapProxyAccounts(userId: string) { const db = usePrisma(); return db.imapProxyAccount.findMany({ where: { userId } }); } export async function upsertImapProxyAccount(data: { userId: string; proxyUsername: string; proxyPassword: string; connectionId: string; }) { const db = usePrisma(); return db.imapProxyAccount.upsert({ where: { connectionId: data.connectionId }, create: data, update: { proxyPassword: data.proxyPassword }, }); } export async function deleteOldMailBlocked(userId: string) { const db = usePrisma(); const cutoff = new Date(Date.now() - 24 * 3_600_000); return db.mailBlocked.deleteMany({ where: { userId, createdAt: { lt: cutoff } }, }); } export async function getMailBlockedPaginated( userId: string, page: number, limit = 20, providerFilter?: string[], ) { const db = usePrisma(); const offset = (page - 1) * limit; // Bei Provider-Filter: JOINen via connectionId → imapHost für Vergleich const whereBase = providerFilter && providerFilter.length > 0 ? { userId, connection: { imapHost: { in: providerFilter } } } : { userId }; const [results, total] = await Promise.all([ db.mailBlocked.findMany({ where: whereBase, orderBy: { createdAt: "desc" }, skip: offset, take: limit, include: { connection: { select: { id: true, email: true, title: true, providerName: true, imapHost: true }, }, }, }), db.mailBlocked.count({ where: whereBase }), ]); return { results, total, page, pages: Math.ceil(total / limit) }; } /** Title einer MailConnection setzen (nullable — reset auf NULL möglich). */ export async function updateMailConnectionTitle( userId: string, connectionId: string, title: string | null, ) { const db = usePrisma(); const updated = await db.mailConnection.updateMany({ where: { id: connectionId, userId }, data: { title }, }); if (updated.count === 0) return null; return db.mailConnection.findFirst({ where: { id: connectionId, userId }, select: { id: true, email: true, title: true }, }); } /** * Geblockte Mails pro Tag (UTC) für die letzten N Tage — für Bar-Chart. * Fehlende Tage werden mit count=0 aufgefüllt. */ export async function getBlockedMailsByDay( userId: string, days: number, ): Promise<{ date: string; count: number }[]> { const db = usePrisma(); const since = new Date(Date.now() - days * 86_400_000); // Prisma hat kein groupBy auf DATE-Funktionen → raw query const rows = await db.$queryRaw<{ date: string; count: bigint }[]>` SELECT TO_CHAR(DATE("created_at"), 'YYYY-MM-DD') AS date, COUNT(*) AS count FROM "rebreak"."mail_blocked" WHERE "user_id" = ${userId}::uuid AND "created_at" >= ${since} GROUP BY DATE("created_at") ORDER BY DATE("created_at") ASC `; const map: Record = {}; for (const row of rows) { map[row.date] = Number(row.count); } // Alle N Tage auffüllen (neueste zuletzt) return Array.from({ length: days }, (_, i) => { const d = new Date(Date.now() - (days - 1 - i) * 86_400_000); const key = d.toISOString().slice(0, 10); return { date: key, count: map[key] ?? 0 }; }); } /** * Anzahl blockierter Mails pro MailConnection — für Half-Donut-Chart. * Connections ohne blocked emails werden NICHT included. */ export async function getBlockedMailsByConnection(userId: string) { const db = usePrisma(); const rows = await db.mailBlocked.groupBy({ by: ["connectionId"], where: { userId }, _count: { id: true }, orderBy: { _count: { id: "desc" } }, }); if (rows.length === 0) return []; const connectionIds = rows.map((r) => r.connectionId); const connections = await db.mailConnection.findMany({ where: { id: { in: connectionIds } }, select: { id: true, email: true, title: true, providerName: true, imapHost: true }, }); const connMap = new Map(connections.map((c) => [c.id, c])); return rows.map((r) => { const conn = connMap.get(r.connectionId); return { connectionId: r.connectionId, title: conn?.title ?? null, email: conn?.email ?? "", providerName: conn?.providerName ?? null, imapHost: conn?.imapHost ?? "", count: r._count.id, }; }); }