import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import * as path from 'path'; import * as fs from 'fs/promises'; import sharp from 'sharp'; const THUMBNAIL_WIDTH = 180; @Injectable() export class EmailPageCacheService { private readonly logger = new Logger(EmailPageCacheService.name); private readonly mailsRoot: string; constructor(configService: ConfigService) { this.mailsRoot = configService.get( 'MAILS_DATA_DIR', '/mnt/data/mails', ); } attachmentDir(attachmentId: number | string): string { return path.join(this.mailsRoot, attachmentId.toString()); } previewPath(attachmentId: number | string, page: number): string { return path.join( this.attachmentDir(attachmentId), `page-${page}.preview.png`, ); } thumbnailPath(attachmentId: number | string, page: number): string { return path.join( this.attachmentDir(attachmentId), `page-${page}.thumb.png`, ); } async generate( attachmentId: number | string, renderedImages: string[], ): Promise { const dir = this.attachmentDir(attachmentId); await fs.mkdir(dir, { recursive: true }); for (let i = 0; i < renderedImages.length; i++) { const page = i + 1; const src = renderedImages[i]; const previewDest = this.previewPath(attachmentId, page); const thumbDest = this.thumbnailPath(attachmentId, page); try { await fs.copyFile(src, previewDest); await sharp(src) .resize({ width: THUMBNAIL_WIDTH }) .png() .toFile(thumbDest); } catch (err: any) { this.logger.warn( `E-Mail Page Cache fehlgeschlagen (Attachment ${attachmentId} Seite ${page}): ${err.message}`, ); } } } async hasPreview( attachmentId: number | string, page: number, ): Promise { try { await fs.access(this.previewPath(attachmentId, page)); return true; } catch { return false; } } }