/** * Branding Service * Manages custom branding settings for PDF generation */ import { randomUUID } from 'crypto'; import { promises as fs } from 'fs'; import path from 'path'; export interface BrandingConfig { id: string; name: string; // Font references (stored separately) titleFont?: string; bodyFont?: string; labelFont?: string; // Colors primaryColor?: string; secondaryColor?: string; accentColor?: string; // Logo logoData?: string; // base64 encoded image // Metadata createdAt: Date; updatedAt: Date; } class BrandingService { private configs: Map = new Map(); private storagePath: string; constructor() { this.storagePath = path.join(process.cwd(), 'storage', 'branding'); this.ensureStorageDirectory(); this.loadDefaultBranding(); } private async ensureStorageDirectory() { try { await fs.mkdir(this.storagePath, { recursive: true }); } catch (error) { console.warn('Could not create branding storage directory:', error); } } private loadDefaultBranding() { // Create a default branding configuration const defaultBranding: BrandingConfig = { id: 'default', name: 'Default', createdAt: new Date(), updatedAt: new Date(), }; this.configs.set('default', defaultBranding); } /** * Get a branding configuration by ID */ getBranding(id: string): BrandingConfig | undefined { return this.configs.get(id); } /** * Get the default branding configuration */ getDefaultBranding(): BrandingConfig { return this.configs.get('default')!; } /** * Create or update a branding configuration */ async saveBranding(config: Partial): Promise { const id = config.id || randomUUID(); const existing = this.configs.get(id); const branding: BrandingConfig = { id, name: config.name || 'Custom Branding', titleFont: config.titleFont, bodyFont: config.bodyFont, labelFont: config.labelFont, primaryColor: config.primaryColor, secondaryColor: config.secondaryColor, accentColor: config.accentColor, logoData: config.logoData, createdAt: existing?.createdAt || new Date(), updatedAt: new Date(), }; this.configs.set(id, branding); // Persist to disk await this.persistBranding(id, branding); return branding; } /** * List all branding configurations */ listBrandings(): BrandingConfig[] { return Array.from(this.configs.values()); } /** * Delete a branding configuration */ async deleteBranding(id: string): Promise { if (id === 'default') { throw new Error('Cannot delete default branding'); } const deleted = this.configs.delete(id); if (deleted) { // Remove from disk try { const filePath = path.join(this.storagePath, `${id}.json`); await fs.unlink(filePath); } catch (error) { console.warn(`Could not delete branding file for ${id}:`, error); } } return deleted; } /** * Persist branding configuration to disk */ private async persistBranding(id: string, branding: BrandingConfig) { try { const filePath = path.join(this.storagePath, `${id}.json`); await fs.writeFile(filePath, JSON.stringify(branding, null, 2)); } catch (error) { console.error(`Failed to persist branding ${id}:`, error); } } } export const brandingService = new BrandingService();