Revert "feat: auto-move imported emails to IMAP folder and add 90-day cleanup"
This reverts commit b1b30fe1dd.
This commit is contained in:
@@ -67,9 +67,3 @@ AGRARMONITOR_UPLOAD_CHECK_CRON=0 * * * * * # Upload-Check-Intervall (Standard:
|
||||
# Leer lassen: E-Mails werden ohne Links versendet
|
||||
APP_URL=
|
||||
DAILY_DIGEST_CRON= # Standard: 0 7 * * * (täglich 07:00 Uhr Europe/Berlin)
|
||||
|
||||
# --- IMAP-Ordnerverwaltung ---
|
||||
# Zielordner für importierte E-Mails (wird automatisch angelegt falls nicht vorhanden)
|
||||
IMAP_IMPORTED_FOLDER=importiert
|
||||
# Papierkorb-Ordner für die 90-Tage-Bereinigung (Gmail: "[Gmail]/Papierkorb", Outlook: "Deleted Items")
|
||||
IMAP_TRASH_FOLDER=Trash
|
||||
|
||||
@@ -36,8 +36,6 @@ services:
|
||||
- IMAP_USE_SSL=${IMAP_USE_SSL:-true}
|
||||
- IMAP_USERNAME=${IMAP_USERNAME:-}
|
||||
- IMAP_PASSWORD=${IMAP_PASSWORD:-}
|
||||
- IMAP_IMPORTED_FOLDER=${IMAP_IMPORTED_FOLDER:-importiert}
|
||||
- IMAP_TRASH_FOLDER=${IMAP_TRASH_FOLDER:-Trash}
|
||||
- BELEGNUMMER_GET_URL=${BELEGNUMMER_GET_URL:-}
|
||||
- BELEGNUMMER_SET_URL=${BELEGNUMMER_SET_URL:-}
|
||||
- AGRARMONITOR_BASE_URL=${AGRARMONITOR_BASE_URL:-https://admin7.agrarmonitor.de}
|
||||
|
||||
@@ -34,7 +34,6 @@ export interface MatchedBarcode {
|
||||
export class BarcodeScannerService implements OnApplicationBootstrap {
|
||||
private readonly logger = new Logger(BarcodeScannerService.name);
|
||||
private templatesCache: BarcodeTemplate[] | null = null;
|
||||
private readonly regenerating = new Map<string, Promise<void>>();
|
||||
|
||||
constructor(
|
||||
private readonly pdfService: PdfService,
|
||||
@@ -121,44 +120,6 @@ export class BarcodeScannerService implements OnApplicationBootstrap {
|
||||
return this.matchTemplates(doc.QrCodes ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stellt sicher, dass der Seiten-Cache (thumb/preview-PNGs) für ein Dokument
|
||||
* vorhanden ist. Parallele Aufrufe für dasselbe Dokument warten auf dasselbe
|
||||
* Promise, um doppeltes Rendering zu vermeiden.
|
||||
*/
|
||||
async ensurePageCache(documentId: string, pdfPath: string): Promise<void> {
|
||||
const existing = this.regenerating.get(documentId);
|
||||
if (existing) return existing;
|
||||
|
||||
const work = this.doRegenerateCache(documentId, pdfPath).finally(() => {
|
||||
this.regenerating.delete(documentId);
|
||||
});
|
||||
this.regenerating.set(documentId, work);
|
||||
return work;
|
||||
}
|
||||
|
||||
private async doRegenerateCache(
|
||||
documentId: string,
|
||||
pdfPath: string,
|
||||
): Promise<void> {
|
||||
let images: string[] = [];
|
||||
try {
|
||||
images = await this.pdfService.pdfToImages(pdfPath, 200);
|
||||
await this.pageCache.clear(documentId);
|
||||
await this.pageCache.generate(documentId, images);
|
||||
this.logger.log(
|
||||
`Seiten-Cache regeneriert für ${documentId} (${images.length} Seiten)`,
|
||||
);
|
||||
} catch (err: unknown) {
|
||||
this.logger.warn(
|
||||
`Cache-Regenerierung fehlgeschlagen (${documentId}): ${getErrorMessage(err)}`,
|
||||
);
|
||||
throw err;
|
||||
} finally {
|
||||
await this.pdfService.cleanup(images);
|
||||
}
|
||||
}
|
||||
|
||||
private async matchTemplates(
|
||||
qrCodes: StoredQrCode[],
|
||||
): Promise<MatchedBarcode[]> {
|
||||
|
||||
@@ -56,62 +56,11 @@ export class EmailDownloadService {
|
||||
}
|
||||
}
|
||||
|
||||
private createImapClient(): ImapFlow {
|
||||
return new ImapFlow({
|
||||
host: this.configService.get<string>('IMAP_HOST', ''),
|
||||
port: this.configService.get<number>('IMAP_PORT', 993),
|
||||
secure: this.configService.get<string>('IMAP_USE_SSL', 'true') === 'true',
|
||||
auth: {
|
||||
user: this.configService.get<string>('IMAP_USERNAME', ''),
|
||||
pass: this.configService.get<string>('IMAP_PASSWORD', ''),
|
||||
},
|
||||
logger: false,
|
||||
});
|
||||
}
|
||||
|
||||
@Cron('0 3 * * *', { timeZone: 'Europe/Berlin' })
|
||||
async cleanupImportedEmails(): Promise<void> {
|
||||
if (!this.configService.get<string>('IMAP_HOST')) return;
|
||||
const importedFolder = this.configService.get<string>('IMAP_IMPORTED_FOLDER', 'importiert');
|
||||
const trashFolder = this.configService.get<string>('IMAP_TRASH_FOLDER', 'Trash');
|
||||
const client = this.createImapClient();
|
||||
try {
|
||||
await client.connect();
|
||||
|
||||
// Alte E-Mails (> 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<void> {
|
||||
const host = this.configService.get<string>('IMAP_HOST');
|
||||
const port = this.configService.get<number>('IMAP_PORT', 993);
|
||||
const secure =
|
||||
this.configService.get<string>('IMAP_USE_SSL', 'true') === 'true';
|
||||
const user = this.configService.get<string>('IMAP_USERNAME');
|
||||
const pass = this.configService.get<string>('IMAP_PASSWORD');
|
||||
|
||||
@@ -124,10 +73,16 @@ export class EmailDownloadService {
|
||||
|
||||
this.logger.log('E-Mail Fetch Job gestartet.');
|
||||
|
||||
const client = this.createImapClient();
|
||||
const client = new ImapFlow({
|
||||
host,
|
||||
port,
|
||||
secure,
|
||||
auth: { user, pass },
|
||||
logger: false,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
this.logger.log(`Verbunden mit IMAP-Server ${host}.`);
|
||||
this.logger.log(`Verbunden mit IMAP-Server ${host}:${port}`);
|
||||
|
||||
const lock = await client.getMailboxLock('INBOX');
|
||||
try {
|
||||
|
||||
@@ -12,7 +12,6 @@ import { Task } from '../database/entities/task.entity';
|
||||
import { PaperlessService } from '../paperless/paperless.service';
|
||||
import * as QRCode from 'qrcode';
|
||||
import { EmailPageCacheService } from './email-page-cache.service';
|
||||
import { ImapFolderService } from './imap-folder.service';
|
||||
import { PdfService } from '../preprocessing/pdf.service';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
@@ -54,7 +53,6 @@ export class EmailImportService {
|
||||
private readonly paperlessService: PaperlessService,
|
||||
private readonly pdfService: PdfService,
|
||||
private readonly pageCache: EmailPageCacheService,
|
||||
private readonly imapFolderService: ImapFolderService,
|
||||
) {}
|
||||
|
||||
async ensurePreviews(emailId: number): Promise<void> {
|
||||
@@ -655,12 +653,6 @@ export class EmailImportService {
|
||||
this.logger.log(
|
||||
`Email ${firstAtt.EmailMessageId} als verarbeitet markiert.`,
|
||||
);
|
||||
const emailEntity = await this.emailRepo.findOne({ where: { Id: firstAtt.EmailMessageId } });
|
||||
if (emailEntity) {
|
||||
this.imapFolderService.moveToImportiert(emailEntity.MessageId).catch(err =>
|
||||
this.logger.error('IMAP-Verschieben fehlgeschlagen: ' + err.message),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import { EmailPageCacheService } from './email-page-cache.service';
|
||||
|
||||
import { EmailImportController } from './email-import.controller';
|
||||
import { EmailImportService } from './email-import.service';
|
||||
import { ImapFolderService } from './imap-folder.service';
|
||||
import { CorrespondentEmailMapping } from '../database/entities/correspondent-email-mapping.entity';
|
||||
import { Task } from '../database/entities/task.entity';
|
||||
import { PreprocessingModule } from '../preprocessing/preprocessing.module';
|
||||
@@ -27,7 +26,7 @@ import { PreprocessingModule } from '../preprocessing/preprocessing.module';
|
||||
PreprocessingModule,
|
||||
],
|
||||
controllers: [EmailController, EmailImportController],
|
||||
providers: [EmailImportService, EmailPageCacheService, ImapFolderService],
|
||||
providers: [EmailImportService, EmailPageCacheService],
|
||||
exports: [EmailPageCacheService],
|
||||
})
|
||||
export class EmailModule {}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { ImapFlow } from 'imapflow';
|
||||
|
||||
@Injectable()
|
||||
export class ImapFolderService {
|
||||
private readonly logger = new Logger(ImapFolderService.name);
|
||||
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
private createClient(): ImapFlow {
|
||||
return new ImapFlow({
|
||||
host: this.configService.get<string>('IMAP_HOST', ''),
|
||||
port: this.configService.get<number>('IMAP_PORT', 993),
|
||||
secure: this.configService.get<string>('IMAP_USE_SSL', 'true') === 'true',
|
||||
auth: {
|
||||
user: this.configService.get<string>('IMAP_USERNAME', ''),
|
||||
pass: this.configService.get<string>('IMAP_PASSWORD', ''),
|
||||
},
|
||||
logger: false,
|
||||
});
|
||||
}
|
||||
|
||||
async moveToImportiert(messageId: string): Promise<void> {
|
||||
if (!this.configService.get<string>('IMAP_HOST')) return;
|
||||
|
||||
const importedFolder = this.configService.get<string>('IMAP_IMPORTED_FOLDER', 'importiert');
|
||||
const client = this.createClient();
|
||||
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.`);
|
||||
}
|
||||
|
||||
await client.mailboxOpen('INBOX');
|
||||
const uids = await client.search({ header: { 'message-id': messageId } }, { uid: true });
|
||||
if (Array.isArray(uids) && uids.length > 0) {
|
||||
await client.messageMove(uids, importedFolder, { uid: true });
|
||||
this.logger.log(`E-Mail ${messageId} → "${importedFolder}" verschoben.`);
|
||||
} else {
|
||||
this.logger.warn(`E-Mail ${messageId} nicht in INBOX gefunden (bereits verschoben?).`);
|
||||
}
|
||||
} catch (err: any) {
|
||||
this.logger.error(`IMAP moveToImportiert fehlgeschlagen: ${err.message}`);
|
||||
} finally {
|
||||
await client.logout().catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,7 +232,7 @@ export class InboxService {
|
||||
variant: 'preview' | 'thumbnail',
|
||||
preferredUsername: string | null,
|
||||
): Promise<string> {
|
||||
const { doc, pdfPath } = await this.resolveDocument(id, preferredUsername);
|
||||
const { doc } = await this.resolveDocument(id, preferredUsername);
|
||||
if (!Number.isInteger(page) || page < 1 || page > doc.PageCount) {
|
||||
throw new NotFoundException('Seite nicht gefunden');
|
||||
}
|
||||
@@ -244,12 +244,7 @@ export class InboxService {
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
} catch {
|
||||
try {
|
||||
await this.barcodeScanner.ensurePageCache(doc.Id, pdfPath);
|
||||
await fs.access(filePath);
|
||||
} catch {
|
||||
throw new NotFoundException('Seite nicht gefunden');
|
||||
}
|
||||
throw new NotFoundException('Seite nicht gefunden');
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user