From 184ac3f5cc7f5093544d984068f8de66816c0d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20P=C3=B6ttker?= Date: Thu, 28 May 2026 18:12:02 +0200 Subject: [PATCH] feat: add clickable links to daily digest emails via APP_URL - Read APP_URL and AGRARMONITOR_BASE_URL from config - Render dashboard entries as clickable links in HTML digest email - Add APP_URL and DAILY_DIGEST_CRON to .env.example and docker-compose.yml Co-Authored-By: Claude Sonnet 4.6 --- .env.example | 6 +++ docker-compose.yml | 2 + .../src/daily-digest/daily-digest.service.ts | 42 ++++++++++++------- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/.env.example b/.env.example index 504d47e..f7287a6 100644 --- a/.env.example +++ b/.env.example @@ -61,3 +61,9 @@ AGRARMONITOR_COOKIE_PATH=./data/agrarmonitor-cookies.json AGRARMONITOR_ENCRYPTION_KEY= # optional, 16+ Zeichen für Cookie-Verschlüsselung AGRARMONITOR_POLLING_CRON=0 */30 * * * * # Polling-Intervall (Standard: alle 30 Minuten); leer lassen zum Deaktivieren AGRARMONITOR_UPLOAD_CHECK_CRON=0 * * * * * # Upload-Check-Intervall (Standard: einmal pro Minute); leer lassen zum Deaktivieren + +# --- Täglicher Digest --- +# Basis-URL der App für klickbare Links in Digest-E-Mails (z.B. https://paperless.example.com) +# Leer lassen: E-Mails werden ohne Links versendet +APP_URL= +DAILY_DIGEST_CRON= # Standard: 0 7 * * * (täglich 07:00 Uhr) diff --git a/docker-compose.yml b/docker-compose.yml index 0635a23..12a8d0b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,6 +47,8 @@ services: - AGRARMONITOR_ENCRYPTION_KEY=${AGRARMONITOR_ENCRYPTION_KEY:-} - AGRARMONITOR_POLLING_CRON=${AGRARMONITOR_POLLING_CRON:-} - AGRARMONITOR_UPLOAD_CHECK_CRON=${AGRARMONITOR_UPLOAD_CHECK_CRON:-} + - APP_URL=${APP_URL:-} + - DAILY_DIGEST_CRON=${DAILY_DIGEST_CRON:-} volumes: - /mnt/scans:/mnt/scans - /mnt/paperlessmanager:/mnt/data diff --git a/paperless-backend/src/daily-digest/daily-digest.service.ts b/paperless-backend/src/daily-digest/daily-digest.service.ts index a0d692c..88e54fa 100644 --- a/paperless-backend/src/daily-digest/daily-digest.service.ts +++ b/paperless-backend/src/daily-digest/daily-digest.service.ts @@ -1,4 +1,5 @@ import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { Cron } from '@nestjs/schedule'; import { StatsService, DashboardCounts } from '../stats/stats.service'; import { UserSettingsService } from '../user-settings/user-settings.service'; @@ -7,17 +8,23 @@ import { MailService } from '../postprocessing/mail.service'; @Injectable() export class DailyDigestService { private readonly logger = new Logger(DailyDigestService.name); + private readonly appUrl: string; + private readonly agrarmonitorBaseUrl: string; constructor( private readonly statsService: StatsService, private readonly userSettingsService: UserSettingsService, private readonly mailService: MailService, - ) {} + private readonly configService: ConfigService, + ) { + this.appUrl = this.configService.get('APP_URL', ''); + this.agrarmonitorBaseUrl = this.configService.get('AGRARMONITOR_BASE_URL', ''); + } async sendDigestForUser(userId: string, email: string, preferredUsername?: string) { const today = new Date().toLocaleDateString('de-DE', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); const counts = await this.statsService.getDashboardCounts(preferredUsername); - const html = buildDigestHtml(counts, today); + const html = buildDigestHtml(counts, today, this.appUrl, this.agrarmonitorBaseUrl); const plainText = buildDigestPlainText(counts, today); await this.mailService.sendMail({ to: email, @@ -43,7 +50,7 @@ export class DailyDigestService { for (const sub of subscribers) { try { const counts = await this.statsService.getDashboardCounts(sub.UserPreferredUsername ?? undefined); - const html = buildDigestHtml(counts, today); + const html = buildDigestHtml(counts, today, this.appUrl, this.agrarmonitorBaseUrl); const plainText = buildDigestPlainText(counts, today); await this.mailService.sendMail({ to: sub.UserEmail!, @@ -65,23 +72,26 @@ function countColor(n: number): string { return '#dc2626'; } -function buildDigestHtml(counts: DashboardCounts, today: string): string { - const rows: { label: string; count: number }[] = [ - { label: 'Eingangsbox (Scanner)', count: counts.inbox }, - { label: 'Posteingang', count: counts.posteingang }, - { label: 'Manuell bearbeiten', count: counts.manuell }, - { label: 'Mailpostfach', count: counts.mailpostfach }, - { label: 'In Agrarmonitor', count: counts.agrarmonitor }, +function buildDigestHtml(counts: DashboardCounts, today: string, appUrl: string, agrarmonitorBaseUrl: string): string { + const rows: { label: string; count: number; url: string }[] = [ + { label: 'Eingangsbox (Scanner)', count: counts.inbox, url: appUrl ? `${appUrl}/inbox` : '' }, + { label: 'Posteingang', count: counts.posteingang, url: appUrl ? `${appUrl}/posteingang` : '' }, + { label: 'Manuell bearbeiten', count: counts.manuell, url: appUrl ? `${appUrl}/manuell` : '' }, + { label: 'Mailpostfach', count: counts.mailpostfach, url: appUrl ? `${appUrl}/mailpostfach` : '' }, + { label: 'In Agrarmonitor', count: counts.agrarmonitor, url: agrarmonitorBaseUrl ? `${agrarmonitorBaseUrl}/dateien/eingang#dateien` : '' }, ]; const tableRows = rows - .map( - r => ` + .map(r => { + const labelCell = r.url + ? `${r.label}` + : r.label; + return ` - ${r.label} - ${r.count} - `, - ) + ${labelCell} + ${r.url ? `${r.count}` : r.count} + `; + }) .join(''); return `