diff --git a/paperless-backend/src/webhook/webhook-queue.service.spec.ts b/paperless-backend/src/webhook/webhook-queue.service.spec.ts index bec68d8..29e75a0 100644 --- a/paperless-backend/src/webhook/webhook-queue.service.spec.ts +++ b/paperless-backend/src/webhook/webhook-queue.service.spec.ts @@ -41,16 +41,17 @@ function createQueueRepoMock() { }), })), find: jest.fn(() => { - const sorted = [...store].sort( - (a, b) => a.createdAt.getTime() - b.createdAt.getTime(), - ); + // Simuliert den DB-seitigen Alters-Filter (createdAt <= NOW() - 5000 ms). + const eligible = [...store] + .filter((s) => Date.now() - s.createdAt.getTime() >= 5000) + .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()); return Promise.resolve( - sorted.length + eligible.length ? [ { - documentId: sorted[0].documentId, + documentId: eligible[0].documentId, action: null, - createdAt: sorted[0].createdAt, + createdAt: eligible[0].createdAt, }, ] : [], diff --git a/paperless-backend/src/webhook/webhook-queue.service.ts b/paperless-backend/src/webhook/webhook-queue.service.ts index 1a68268..fd44739 100644 --- a/paperless-backend/src/webhook/webhook-queue.service.ts +++ b/paperless-backend/src/webhook/webhook-queue.service.ts @@ -1,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { Interval } from '@nestjs/schedule'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { Raw, Repository } from 'typeorm'; import { PaperlessProcessorService } from '../paperless/paperless-processor.service'; import { Setting } from '../database/entities/setting.entity'; import { WebhookQueueItem } from '../database/entities/webhook-queue-item.entity'; @@ -99,18 +99,25 @@ export class WebhookQueueService { if (this.isProcessing) return; this.isProcessing = true; try { - // Solange Einträge vorhanden sind, einzeln und sequenziell abarbeiten. + // Solange (alte genug) Einträge vorhanden sind, sequenziell abarbeiten. for (;;) { - // Ältesten Eintrag (FIFO) holen ... + // Ältesten Eintrag (FIFO) holen, der die Mindest-Wartezeit erfüllt. + // Der Altersvergleich läuft bewusst in der DB (NOW() vs. createdAt) und + // ist damit unabhängig von Zeitzone/Uhr des Node-Prozesses – ein Node/DB- + // Zeitversatz würde sonst den Vergleich verfälschen (Einträge nie "alt + // genug"). Nur ein numerisches Konstantenliteral wird interpoliert. const [next] = await this.queueRepo.find({ + where: { + createdAt: Raw( + (alias) => + `${alias} <= (NOW(6) - INTERVAL ${MIN_QUEUE_AGE_MS * 1000} MICROSECOND)`, + ), + }, order: { createdAt: 'ASC' }, take: 1, }); - if (!next) break; - // Mindest-Wartezeit einhalten: ist der älteste Eintrag noch zu jung, - // sind es (FIFO) alle → diesen Tick beenden, beim nächsten erneut prüfen. - if (Date.now() - next.createdAt.getTime() < MIN_QUEUE_AGE_MS) break; - // ... und SOFORT (vor Verarbeitungsstart) aus der Tabelle entfernen. + if (!next) break; // Warteschlange leer oder noch nichts alt genug + // SOFORT (vor Verarbeitungsstart) aus der Tabelle entfernen. await this.queueRepo.delete({ documentId: next.documentId }); await this.handleDocument(next.documentId); }