diff --git a/paperless-backend/src/email-download/email-download.service.ts b/paperless-backend/src/email-download/email-download.service.ts index 83b40b7..5ff38cc 100644 --- a/paperless-backend/src/email-download/email-download.service.ts +++ b/paperless-backend/src/email-download/email-download.service.ts @@ -68,47 +68,6 @@ export class EmailDownloadService { }); } - @Cron('0 3 * * *', { timeZone: 'Europe/Berlin' }) - async cleanupImportedEmails(): Promise { - if (!this.configService.get('IMAP_HOST')) return; - const importedFolder = this.configService.get('IMAP_IMPORTED_FOLDER', 'importiert'); - const trashFolder = this.configService.get('IMAP_TRASH_FOLDER', 'Trash'); - const client = this.createImapClient(); - try { - await client.connect(); - - // E-Mails älter als 90 Tage in Papierkorb verschieben - try { - await client.mailboxOpen(importedFolder); - const cutoff = new Date(); - cutoff.setDate(cutoff.getDate() - 90); - const oldUids = await client.search({ before: cutoff }, { uid: true }); - if (Array.isArray(oldUids) && oldUids.length > 0) { - await client.messageMove(oldUids, trashFolder, { uid: true }); - this.logger.log(`${oldUids.length} alte E-Mail(s) aus "${importedFolder}" in "${trashFolder}" verschoben.`); - } - } catch (err: any) { - this.logger.warn(`Bereinigung "${importedFolder}" nicht möglich: ${err.message}`); - } - - // Papierkorb leeren - try { - await client.mailboxOpen(trashFolder); - const trashUids = await client.search({ all: true }, { uid: true }); - if (Array.isArray(trashUids) && trashUids.length > 0) { - await client.messageDelete(trashUids, { uid: true }); - this.logger.log(`${trashUids.length} E-Mail(s) aus "${trashFolder}" gelöscht.`); - } - } catch (err: any) { - this.logger.warn(`Papierkorb "${trashFolder}" konnte nicht geleert werden: ${err.message}`); - } - } catch (err: any) { - this.logger.error(`IMAP-Cleanup fehlgeschlagen: ${err.message}`); - } finally { - await client.logout().catch(() => {}); - } - } - private async fetchAndStore(): Promise { const host = this.configService.get('IMAP_HOST'); const user = this.configService.get('IMAP_USERNAME'); diff --git a/paperless-backend/src/email/email.controller.ts b/paperless-backend/src/email/email.controller.ts index dc18b9b..2b0b0d2 100644 --- a/paperless-backend/src/email/email.controller.ts +++ b/paperless-backend/src/email/email.controller.ts @@ -17,6 +17,7 @@ import { Email } from '../database/entities/email.entity'; import { Attachment } from '../database/entities/attachment.entity'; import { Content } from '../database/entities/content.entity'; import { PaperlessService } from '../paperless/paperless.service'; +import { ImapFolderService } from './imap-folder.service'; import { RequirePermissions } from '../auth/permissions.decorator'; import { Permission } from '../auth/permissions.enum'; @@ -31,6 +32,7 @@ export class EmailController { @InjectRepository(Content) private readonly contentRepo: Repository, private readonly paperlessService: PaperlessService, + private readonly imapFolderService: ImapFolderService, ) {} @Get() @@ -202,7 +204,21 @@ export class EmailController { this.logger.log( `Prüfung abgeschlossen. ${updatedCount} E-Mails aktualisiert, ${idsUpdated} Paperless-IDs ergänzt, ${skippedCount} übersprungen.`, ); - return { updatedCount, idsUpdated }; + this.imapFolderService.cleanupImportedEmails().catch(err => + this.logger.error('IMAP-Cleanup fehlgeschlagen: ' + err.message), + ); + + let movedToImportiert = 0; + if (body.includeProcessed) { + const processedEmails = await this.emailRepo.find({ + where: [{ Status: 1 }, { Status: 3 }], + select: ['MessageId'], + }); + const messageIds = processedEmails.map((e) => e.MessageId).filter(Boolean); + movedToImportiert = await this.imapFolderService.moveProcessedInboxToImportiert(messageIds); + } + + return { updatedCount, idsUpdated, movedToImportiert }; } catch (error: any) { this.logger.error( `Kritischer Fehler bei checkAttachments: ${error.message}`, diff --git a/paperless-backend/src/email/imap-folder.service.ts b/paperless-backend/src/email/imap-folder.service.ts index c4ff0c5..4fd4733 100644 --- a/paperless-backend/src/email/imap-folder.service.ts +++ b/paperless-backend/src/email/imap-folder.service.ts @@ -1,5 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { Cron } from '@nestjs/schedule'; import { ImapFlow } from 'imapflow'; @Injectable() @@ -21,6 +22,91 @@ export class ImapFolderService { }); } + @Cron('0 3 * * *', { timeZone: 'Europe/Berlin' }) + async cleanupImportedEmails(): Promise { + if (!this.configService.get('IMAP_HOST')) return; + const importedFolder = this.configService.get('IMAP_IMPORTED_FOLDER', 'importiert'); + const trashFolder = this.configService.get('IMAP_TRASH_FOLDER', 'Trash'); + const client = this.createClient(); + try { + await client.connect(); + + // E-Mails älter als 90 Tage in Papierkorb verschieben + try { + await client.mailboxOpen(importedFolder); + const cutoff = new Date(); + cutoff.setDate(cutoff.getDate() - 90); + const oldUids = await client.search({ before: cutoff }, { uid: true }); + if (Array.isArray(oldUids) && oldUids.length > 0) { + await client.messageMove(oldUids, trashFolder, { uid: true }); + this.logger.log(`${oldUids.length} alte E-Mail(s) aus "${importedFolder}" in "${trashFolder}" verschoben.`); + } + } catch (err: any) { + this.logger.warn(`Bereinigung "${importedFolder}" nicht möglich: ${err.message}`); + } + + // Papierkorb leeren + try { + await client.mailboxOpen(trashFolder); + const trashUids = await client.search({ all: true }, { uid: true }); + if (Array.isArray(trashUids) && trashUids.length > 0) { + await client.messageDelete(trashUids, { uid: true }); + this.logger.log(`${trashUids.length} E-Mail(s) aus "${trashFolder}" gelöscht.`); + } + } catch (err: any) { + this.logger.warn(`Papierkorb "${trashFolder}" konnte nicht geleert werden: ${err.message}`); + } + } catch (err: any) { + this.logger.error(`IMAP-Cleanup fehlgeschlagen: ${err.message}`); + } finally { + await client.logout().catch(() => {}); + } + } + + async moveProcessedInboxToImportiert(messageIds: string[]): Promise { + if (!this.configService.get('IMAP_HOST') || messageIds.length === 0) return 0; + + const importedFolder = this.configService.get('IMAP_IMPORTED_FOLDER', 'importiert'); + const client = this.createClient(); + let movedCount = 0; + + try { + await client.connect(); + + const mailboxes = await client.list(); + if (!mailboxes.some(m => m.path === importedFolder)) { + await client.mailboxCreate(importedFolder); + this.logger.log(`IMAP-Ordner "${importedFolder}" erstellt.`); + } + + const status = await client.mailboxOpen('INBOX'); + if (status.exists === 0) return 0; + + const normalize = (id: string) => id.replace(/^<|>$/g, '').toLowerCase(); + const idSet = new Set(messageIds.map(normalize)); + const uidsToMove: number[] = []; + + for await (const msg of client.fetch('1:*', { uid: true, envelope: true })) { + const msgId = msg.envelope?.messageId; + if (msgId && idSet.has(normalize(msgId))) { + uidsToMove.push(msg.uid); + } + } + + if (uidsToMove.length > 0) { + await client.messageMove(uidsToMove, importedFolder, { uid: true }); + movedCount = uidsToMove.length; + this.logger.log(`${movedCount} E-Mail(s) aus INBOX → "${importedFolder}" verschoben.`); + } + } catch (err: any) { + this.logger.error(`moveProcessedInboxToImportiert fehlgeschlagen: ${err.message}`); + } finally { + await client.logout().catch(() => {}); + } + + return movedCount; + } + async moveToImportiert(messageId: string): Promise { if (!this.configService.get('IMAP_HOST')) return; diff --git a/paperless-frontend/src/api/emails.ts b/paperless-frontend/src/api/emails.ts index b006378..f7c0915 100644 --- a/paperless-frontend/src/api/emails.ts +++ b/paperless-frontend/src/api/emails.ts @@ -44,7 +44,7 @@ export const emailsApi = { api.post<{ message: string }>('/api/emails/fetch').then((r) => r.data), checkAttachments: (includeProcessed = false) => - api.post<{ updatedCount: number; idsUpdated: number }>('/api/emails/check-attachments', { includeProcessed }).then((r) => r.data), + api.post<{ updatedCount: number; idsUpdated: number; movedToImportiert: number }>('/api/emails/check-attachments', { includeProcessed }).then((r) => r.data), updateStatus: (id: number, status: number) => api.patch<{ message?: string }>(`/api/emails/${id}/status`, { status }).then((r) => r.data), diff --git a/paperless-frontend/src/pages/MailpostfachPage.tsx b/paperless-frontend/src/pages/MailpostfachPage.tsx index 5eec79f..a865c79 100644 --- a/paperless-frontend/src/pages/MailpostfachPage.tsx +++ b/paperless-frontend/src/pages/MailpostfachPage.tsx @@ -172,6 +172,7 @@ export default function MailpostfachPage() { const parts = []; if (result.updatedCount > 0) parts.push(`${result.updatedCount} E-Mail(s) aktualisiert`); if (result.idsUpdated > 0) parts.push(`${result.idsUpdated} Paperless-ID(s) ergänzt`); + if (result.movedToImportiert > 0) parts.push(`${result.movedToImportiert} E-Mail(s) in „importiert" verschoben`); message.success(parts.length > 0 ? parts.join(', ') + '.' : 'Keine Änderungen.'); if (result.updatedCount > 0 || result.idsUpdated > 0) await loadData(); } catch {