feat: filter digest tiles by user permissions and add import progress status
Build and Push Multi-Platform Images / build-and-push (push) Successful in 42s

- Store UserGroups from OIDC in UserSettings entity, sync on each request
- Filter daily digest tiles based on user's permission groups
- Add in-memory job status tracking to EmailImportService
- Poll import job status in MailImportWizard and show progress in Spin tip

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 16:29:56 +02:00
parent 2747b0046a
commit 4c75a1ded2
9 changed files with 116 additions and 78 deletions
@@ -21,6 +21,16 @@ import * as crypto from 'crypto';
@Injectable()
export class EmailImportService {
private readonly logger = new Logger(EmailImportService.name);
private readonly importJobs = new Map<string, { message: string; done: boolean }>();
private setJobStatus(jobId: string | undefined, message: string, done = false): void {
if (!jobId) return;
this.importJobs.set(jobId, { message, done });
}
getJobStatus(jobId: string): { message: string; done: boolean } | null {
return this.importJobs.get(jobId) ?? null;
}
constructor(
private readonly configService: ConfigService,
@@ -328,12 +338,13 @@ export class EmailImportService {
// --- Import Logic ---
async executeImport(data: {
jobId?: string;
attachments: {
attachmentId: number;
type: 'MAIN' | 'ATTACHMENT' | 'IGNORE';
paperlessCorrespondentId?: number | null;
parentDocumentId?: number | null; // Used if type is ATTACHMENT (should map to a Custom Field theoretically, or just tags. For now, CF if configured, but we pass it)
splitRanges?: { start: number; end: number }[]; // 1-based pages, e.g. [{start: 1, end: 3}, {start: 4, end: 5}]
parentDocumentId?: number | null;
splitRanges?: { start: number; end: number }[];
barcode?: { x: number; y: number; nummer: string; datum: string; jahr: string };
belegnummer?: string;
}[];
@@ -341,6 +352,7 @@ export class EmailImportService {
}): Promise<{ success: boolean; results: any[] }> {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'paperless-mail-import-'));
const results = [];
this.setJobStatus(data.jobId, 'Dokumente werden vorbereitet...');
try {
for (const att of data.attachments) {
@@ -417,8 +429,9 @@ export class EmailImportService {
};
if (att.paperlessCorrespondentId) options.correspondent = att.paperlessCorrespondentId;
this.setJobStatus(data.jobId, `Lade ${uploadItem.filename} hoch...`);
const paperlessTaskId = await this.paperlessService.uploadDocument(uploadItem.path, options);
// Create background task for enrichment (same logic as Inbox)
const backgroundTask = this.taskRepo.create({
TaskId: paperlessTaskId,
@@ -437,6 +450,7 @@ export class EmailImportService {
// Still poll for Doc ID so we can return it to the frontend for immediate preview
let docId = null;
for (let i = 0; i < 30; i++) {
this.setJobStatus(data.jobId, `Warte auf Paperless-Verarbeitung... (${i + 1}/30)`);
await new Promise(resolve => setTimeout(resolve, 2000));
try {
const taskStatus = await this.paperlessService.getTask(paperlessTaskId);
@@ -481,10 +495,12 @@ export class EmailImportService {
}
}
this.setJobStatus(data.jobId, 'Import abgeschlossen', true);
return { success: true, results };
} finally {
// Clean up temp dir
// Clean up temp dir and job status
await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});
if (data.jobId) setTimeout(() => this.importJobs.delete(data.jobId!), 5000);
}
}
}