// ==================== EMAIL PROVIDER SERVICE ==================== import { PrismaClient } from '@prisma/client'; import { decrypt } from '../../utils/encryption.js'; import { IEmailProvider, EmailProviderConfig, EmailExistsResult, EmailOperationResult, CreateEmailParams, } from './types.js'; import { PleskEmailProvider } from './pleskProvider.js'; const prisma = new PrismaClient(); // Factory-Funktion um den richtigen Provider zu erstellen function createProvider(config: EmailProviderConfig): IEmailProvider { switch (config.type) { case 'PLESK': return new PleskEmailProvider(config); case 'CPANEL': // TODO: cPanel Provider implementieren throw new Error('cPanel Provider noch nicht implementiert'); case 'DIRECTADMIN': // TODO: DirectAdmin Provider implementieren throw new Error('DirectAdmin Provider noch nicht implementiert'); default: throw new Error(`Unbekannter Provider-Typ: ${config.type}`); } } // ==================== CONFIG CRUD ==================== export async function getAllProviderConfigs() { return prisma.emailProviderConfig.findMany({ orderBy: [{ isDefault: 'desc' }, { name: 'asc' }], }); } export async function getProviderConfigById(id: number) { return prisma.emailProviderConfig.findUnique({ where: { id }, }); } export async function getDefaultProviderConfig() { return prisma.emailProviderConfig.findFirst({ where: { isActive: true, isDefault: true }, }); } export async function getActiveProviderConfig() { // Erst Default-Provider versuchen, dann irgendeinen aktiven const defaultProvider = await getDefaultProviderConfig(); if (defaultProvider) return defaultProvider; return prisma.emailProviderConfig.findFirst({ where: { isActive: true }, }); } export interface CreateProviderConfigData { name: string; type: 'PLESK' | 'CPANEL' | 'DIRECTADMIN'; apiUrl: string; apiKey?: string; username?: string; password?: string; domain: string; defaultForwardEmail?: string; isActive?: boolean; isDefault?: boolean; } export async function createProviderConfig(data: CreateProviderConfigData) { // Falls isDefault=true, alle anderen auf false setzen if (data.isDefault) { await prisma.emailProviderConfig.updateMany({ where: { isDefault: true }, data: { isDefault: false }, }); } // Passwort verschlüsseln falls vorhanden const { encrypt } = await import('../../utils/encryption.js'); const passwordEncrypted = data.password ? encrypt(data.password) : null; return prisma.emailProviderConfig.create({ data: { name: data.name, type: data.type, apiUrl: data.apiUrl, apiKey: data.apiKey || null, username: data.username || null, passwordEncrypted, domain: data.domain, defaultForwardEmail: data.defaultForwardEmail || null, isActive: data.isActive ?? true, isDefault: data.isDefault ?? false, }, }); } export async function updateProviderConfig( id: number, data: Partial ) { // Falls isDefault=true, alle anderen auf false setzen if (data.isDefault) { await prisma.emailProviderConfig.updateMany({ where: { isDefault: true, id: { not: id } }, data: { isDefault: false }, }); } const updateData: Record = {}; if (data.name !== undefined) updateData.name = data.name; if (data.type !== undefined) updateData.type = data.type; if (data.apiUrl !== undefined) updateData.apiUrl = data.apiUrl; if (data.apiKey !== undefined) updateData.apiKey = data.apiKey || null; if (data.username !== undefined) updateData.username = data.username || null; if (data.domain !== undefined) updateData.domain = data.domain; if (data.defaultForwardEmail !== undefined) updateData.defaultForwardEmail = data.defaultForwardEmail || null; if (data.isActive !== undefined) updateData.isActive = data.isActive; if (data.isDefault !== undefined) updateData.isDefault = data.isDefault; // Passwort-Logik: // - Wenn neues Passwort übergeben → verschlüsseln und speichern // - Wenn Benutzername gelöscht wird → Passwort auch löschen (gehören zusammen) if (data.password) { const { encrypt } = await import('../../utils/encryption.js'); updateData.passwordEncrypted = encrypt(data.password); } else if (data.username !== undefined && !data.username) { // Benutzername wird gelöscht → Passwort auch löschen updateData.passwordEncrypted = null; } return prisma.emailProviderConfig.update({ where: { id }, data: updateData, }); } export async function deleteProviderConfig(id: number) { return prisma.emailProviderConfig.delete({ where: { id }, }); } // ==================== EMAIL OPERATIONS ==================== // Provider-Instanz aus DB-Config erstellen async function getProviderInstance(): Promise { const dbConfig = await getActiveProviderConfig(); if (!dbConfig) { throw new Error('Kein aktiver Email-Provider konfiguriert'); } // Passwort entschlüsseln let password: string | undefined; if (dbConfig.passwordEncrypted) { try { password = decrypt(dbConfig.passwordEncrypted); } catch { console.error('Konnte Passwort nicht entschlüsseln'); } } const config: EmailProviderConfig = { id: dbConfig.id, name: dbConfig.name, type: dbConfig.type as 'PLESK' | 'CPANEL' | 'DIRECTADMIN', apiUrl: dbConfig.apiUrl, apiKey: dbConfig.apiKey || undefined, username: dbConfig.username || undefined, password, domain: dbConfig.domain, defaultForwardEmail: dbConfig.defaultForwardEmail || undefined, isActive: dbConfig.isActive, isDefault: dbConfig.isDefault, }; return createProvider(config); } // Prüfen ob eine E-Mail existiert export async function checkEmailExists(localPart: string): Promise { try { const provider = await getProviderInstance(); return provider.emailExists(localPart); } catch (error) { console.error('checkEmailExists error:', error); return { exists: false }; } } // E-Mail erstellen mit Weiterleitungen export async function provisionEmail( localPart: string, customerEmail: string ): Promise { try { const provider = await getProviderInstance(); const config = await getActiveProviderConfig(); // Weiterleitungsziele zusammenstellen const forwardTargets: string[] = [customerEmail]; // Unsere eigene Weiterleitungsadresse hinzufügen falls konfiguriert if (config?.defaultForwardEmail) { forwardTargets.push(config.defaultForwardEmail); } // Prüfen ob existiert const exists = await provider.emailExists(localPart); if (exists.exists) { return { success: true, message: `E-Mail ${exists.email} existiert bereits`, }; } // Erstellen const result = await provider.createEmail({ localPart, forwardTargets, }); return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler'; return { success: false, error: errorMessage, }; } } // E-Mail löschen export async function deprovisionEmail(localPart: string): Promise { try { const provider = await getProviderInstance(); return provider.deleteEmail(localPart); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler'; return { success: false, error: errorMessage, }; } } // E-Mail umbenennen export async function renameProvisionedEmail( oldLocalPart: string, newLocalPart: string ): Promise { try { const provider = await getProviderInstance(); return provider.renameEmail({ oldLocalPart, newLocalPart }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler'; return { success: false, error: errorMessage, }; } } // Domain aus aktivem Provider holen export async function getProviderDomain(): Promise { const config = await getActiveProviderConfig(); return config?.domain || null; } // Provider-Instanz aus übergebener Config erstellen (für Tests mit ungespeicherten Daten) function createProviderFromFormData(data: { type: 'PLESK' | 'CPANEL' | 'DIRECTADMIN'; apiUrl: string; apiKey?: string; username?: string; password?: string; domain: string; }): IEmailProvider { const config: EmailProviderConfig = { id: 0, name: 'Test', type: data.type, apiUrl: data.apiUrl, apiKey: data.apiKey, username: data.username, password: data.password, domain: data.domain, isActive: true, isDefault: false, }; return createProvider(config); } // Provider-Instanz aus DB-Config per ID erstellen async function getProviderInstanceById(id: number): Promise { const dbConfig = await getProviderConfigById(id); if (!dbConfig) { throw new Error('Email-Provider nicht gefunden'); } // Passwort entschlüsseln let password: string | undefined; if (dbConfig.passwordEncrypted) { try { password = decrypt(dbConfig.passwordEncrypted); } catch { console.error('Konnte Passwort nicht entschlüsseln'); } } const config: EmailProviderConfig = { id: dbConfig.id, name: dbConfig.name, type: dbConfig.type as 'PLESK' | 'CPANEL' | 'DIRECTADMIN', apiUrl: dbConfig.apiUrl, apiKey: dbConfig.apiKey || undefined, username: dbConfig.username || undefined, password, domain: dbConfig.domain, defaultForwardEmail: dbConfig.defaultForwardEmail || undefined, isActive: dbConfig.isActive, isDefault: dbConfig.isDefault, }; return createProvider(config); } // Provider-Verbindung testen (mit ID, Formulardaten oder Default-Provider) export async function testProviderConnection(options?: { id?: number; testData?: { type: 'PLESK' | 'CPANEL' | 'DIRECTADMIN'; apiUrl: string; apiKey?: string; username?: string; password?: string; domain: string; }; }): Promise { try { let provider: IEmailProvider; if (options?.testData) { // Mit übergebenen Daten testen (z.B. aus Modal beim Neuanlegen) provider = createProviderFromFormData(options.testData); } else if (options?.id) { // Gespeicherten Provider per ID testen provider = await getProviderInstanceById(options.id); } else { // Default-Provider testen provider = await getProviderInstance(); } // Expliziter Verbindungstest (wirft Fehler bei Auth-Problemen) await provider.testConnection(); return { success: true, message: 'Verbindung zum Email-Provider erfolgreich', }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler'; return { success: false, error: errorMessage, }; } }