feat: add clickable links to daily digest emails via APP_URL
Build and Push Multi-Platform Images / build-and-push (push) Successful in 34s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 34s
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -61,3 +61,9 @@ AGRARMONITOR_COOKIE_PATH=./data/agrarmonitor-cookies.json
|
|||||||
AGRARMONITOR_ENCRYPTION_KEY= # optional, 16+ Zeichen für Cookie-Verschlüsselung
|
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_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
|
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)
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ services:
|
|||||||
- AGRARMONITOR_ENCRYPTION_KEY=${AGRARMONITOR_ENCRYPTION_KEY:-}
|
- AGRARMONITOR_ENCRYPTION_KEY=${AGRARMONITOR_ENCRYPTION_KEY:-}
|
||||||
- AGRARMONITOR_POLLING_CRON=${AGRARMONITOR_POLLING_CRON:-}
|
- AGRARMONITOR_POLLING_CRON=${AGRARMONITOR_POLLING_CRON:-}
|
||||||
- AGRARMONITOR_UPLOAD_CHECK_CRON=${AGRARMONITOR_UPLOAD_CHECK_CRON:-}
|
- AGRARMONITOR_UPLOAD_CHECK_CRON=${AGRARMONITOR_UPLOAD_CHECK_CRON:-}
|
||||||
|
- APP_URL=${APP_URL:-}
|
||||||
|
- DAILY_DIGEST_CRON=${DAILY_DIGEST_CRON:-}
|
||||||
volumes:
|
volumes:
|
||||||
- /mnt/scans:/mnt/scans
|
- /mnt/scans:/mnt/scans
|
||||||
- /mnt/paperlessmanager:/mnt/data
|
- /mnt/paperlessmanager:/mnt/data
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { Cron } from '@nestjs/schedule';
|
import { Cron } from '@nestjs/schedule';
|
||||||
import { StatsService, DashboardCounts } from '../stats/stats.service';
|
import { StatsService, DashboardCounts } from '../stats/stats.service';
|
||||||
import { UserSettingsService } from '../user-settings/user-settings.service';
|
import { UserSettingsService } from '../user-settings/user-settings.service';
|
||||||
@@ -7,17 +8,23 @@ import { MailService } from '../postprocessing/mail.service';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class DailyDigestService {
|
export class DailyDigestService {
|
||||||
private readonly logger = new Logger(DailyDigestService.name);
|
private readonly logger = new Logger(DailyDigestService.name);
|
||||||
|
private readonly appUrl: string;
|
||||||
|
private readonly agrarmonitorBaseUrl: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly statsService: StatsService,
|
private readonly statsService: StatsService,
|
||||||
private readonly userSettingsService: UserSettingsService,
|
private readonly userSettingsService: UserSettingsService,
|
||||||
private readonly mailService: MailService,
|
private readonly mailService: MailService,
|
||||||
) {}
|
private readonly configService: ConfigService,
|
||||||
|
) {
|
||||||
|
this.appUrl = this.configService.get<string>('APP_URL', '');
|
||||||
|
this.agrarmonitorBaseUrl = this.configService.get<string>('AGRARMONITOR_BASE_URL', '');
|
||||||
|
}
|
||||||
|
|
||||||
async sendDigestForUser(userId: string, email: string, preferredUsername?: string) {
|
async sendDigestForUser(userId: string, email: string, preferredUsername?: string) {
|
||||||
const today = new Date().toLocaleDateString('de-DE', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
|
const today = new Date().toLocaleDateString('de-DE', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
|
||||||
const counts = await this.statsService.getDashboardCounts(preferredUsername);
|
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);
|
const plainText = buildDigestPlainText(counts, today);
|
||||||
await this.mailService.sendMail({
|
await this.mailService.sendMail({
|
||||||
to: email,
|
to: email,
|
||||||
@@ -43,7 +50,7 @@ export class DailyDigestService {
|
|||||||
for (const sub of subscribers) {
|
for (const sub of subscribers) {
|
||||||
try {
|
try {
|
||||||
const counts = await this.statsService.getDashboardCounts(sub.UserPreferredUsername ?? undefined);
|
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);
|
const plainText = buildDigestPlainText(counts, today);
|
||||||
await this.mailService.sendMail({
|
await this.mailService.sendMail({
|
||||||
to: sub.UserEmail!,
|
to: sub.UserEmail!,
|
||||||
@@ -65,23 +72,26 @@ function countColor(n: number): string {
|
|||||||
return '#dc2626';
|
return '#dc2626';
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildDigestHtml(counts: DashboardCounts, today: string): string {
|
function buildDigestHtml(counts: DashboardCounts, today: string, appUrl: string, agrarmonitorBaseUrl: string): string {
|
||||||
const rows: { label: string; count: number }[] = [
|
const rows: { label: string; count: number; url: string }[] = [
|
||||||
{ label: 'Eingangsbox (Scanner)', count: counts.inbox },
|
{ label: 'Eingangsbox (Scanner)', count: counts.inbox, url: appUrl ? `${appUrl}/inbox` : '' },
|
||||||
{ label: 'Posteingang', count: counts.posteingang },
|
{ label: 'Posteingang', count: counts.posteingang, url: appUrl ? `${appUrl}/posteingang` : '' },
|
||||||
{ label: 'Manuell bearbeiten', count: counts.manuell },
|
{ label: 'Manuell bearbeiten', count: counts.manuell, url: appUrl ? `${appUrl}/manuell` : '' },
|
||||||
{ label: 'Mailpostfach', count: counts.mailpostfach },
|
{ label: 'Mailpostfach', count: counts.mailpostfach, url: appUrl ? `${appUrl}/mailpostfach` : '' },
|
||||||
{ label: 'In Agrarmonitor', count: counts.agrarmonitor },
|
{ label: 'In Agrarmonitor', count: counts.agrarmonitor, url: agrarmonitorBaseUrl ? `${agrarmonitorBaseUrl}/dateien/eingang#dateien` : '' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const tableRows = rows
|
const tableRows = rows
|
||||||
.map(
|
.map(r => {
|
||||||
r => `
|
const labelCell = r.url
|
||||||
|
? `<a href="${r.url}" style="color:#1d4ed8;text-decoration:none;">${r.label}</a>`
|
||||||
|
: r.label;
|
||||||
|
return `
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:10px 16px;border-bottom:1px solid #e5e7eb;font-family:sans-serif;font-size:14px;color:#374151;">${r.label}</td>
|
<td style="padding:10px 16px;border-bottom:1px solid #e5e7eb;font-family:sans-serif;font-size:14px;color:#374151;">${labelCell}</td>
|
||||||
<td style="padding:10px 16px;border-bottom:1px solid #e5e7eb;text-align:center;font-family:sans-serif;font-size:16px;font-weight:bold;color:${countColor(r.count)};">${r.count}</td>
|
<td style="padding:10px 16px;border-bottom:1px solid #e5e7eb;text-align:center;font-family:sans-serif;font-size:16px;font-weight:bold;color:${countColor(r.count)};">${r.url ? `<a href="${r.url}" style="color:${countColor(r.count)};text-decoration:none;">${r.count}</a>` : r.count}</td>
|
||||||
</tr>`,
|
</tr>`;
|
||||||
)
|
})
|
||||||
.join('');
|
.join('');
|
||||||
|
|
||||||
return `<!DOCTYPE html>
|
return `<!DOCTYPE html>
|
||||||
|
|||||||
Reference in New Issue
Block a user