added backup and email client

This commit is contained in:
2026-02-01 00:02:35 +01:00
parent ff857be01a
commit e4fdfbc95f
210 changed files with 24211 additions and 742 deletions
+14
View File
@@ -247,10 +247,12 @@ export declare function getContractById(id: number, decryptPassword?: boolean):
createdAt: Date;
updatedAt: Date;
notes: string | null;
hasMailbox: boolean;
platform: string | null;
isProvisioned: boolean;
provisionedAt: Date | null;
provisionError: string | null;
emailPasswordEncrypted: string | null;
} | null;
carInsuranceDetails: {
id: number;
@@ -511,6 +513,11 @@ export declare function getContractById(id: number, decryptPassword?: boolean):
portalUsername: string | null;
stressfreiEmailId: number | null;
}) | null;
followUpContract: {
id: number;
status: import(".prisma/client").$Enums.ContractStatus;
contractNumber: string;
} | null;
} & {
id: number;
customerId: number;
@@ -936,10 +943,12 @@ export declare function updateContract(id: number, data: Partial<ContractCreateD
createdAt: Date;
updatedAt: Date;
notes: string | null;
hasMailbox: boolean;
platform: string | null;
isProvisioned: boolean;
provisionedAt: Date | null;
provisionError: string | null;
emailPasswordEncrypted: string | null;
} | null;
carInsuranceDetails: {
id: number;
@@ -1200,6 +1209,11 @@ export declare function updateContract(id: number, data: Partial<ContractCreateD
portalUsername: string | null;
stressfreiEmailId: number | null;
}) | null;
followUpContract: {
id: number;
status: import(".prisma/client").$Enums.ContractStatus;
contractNumber: string;
} | null;
} & {
id: number;
customerId: number;
File diff suppressed because one or more lines are too long
+32 -1
View File
@@ -27,8 +27,14 @@ async function getAllContracts(filters) {
}
if (type)
where.type = type;
if (status)
// Status-Filter: Deaktivierte Verträge standardmäßig ausblenden
if (status) {
where.status = status;
}
else {
// Wenn kein Status-Filter gesetzt, alle außer DEACTIVATED anzeigen
where.status = { not: client_1.ContractStatus.DEACTIVATED };
}
if (search) {
where.OR = [
// Basis-Vertragsfelder
@@ -125,6 +131,9 @@ async function getContractById(id, decryptPassword = false) {
tvDetails: true,
carInsuranceDetails: true,
stressfreiEmail: true,
followUpContract: {
select: { id: true, contractNumber: true, status: true },
},
},
});
if (!contract)
@@ -412,6 +421,19 @@ async function updateContract(id, data) {
return getContractById(id);
}
async function deleteContract(id) {
// Vertragskette erhalten beim Löschen:
// Wenn A → B → C und B gelöscht wird, soll C direkt auf A zeigen (A → C)
// 1. Zu löschenden Vertrag holen um dessen Vorgänger zu kennen
const contractToDelete = await prisma.contract.findUnique({
where: { id },
select: { previousContractId: true },
});
// 2. Folgevertrag(e) mit dem Vorgänger des gelöschten Vertrags verbinden
// So bleibt die Kette erhalten: A → B → C wird zu A → C
await prisma.contract.updateMany({
where: { previousContractId: id },
data: { previousContractId: contractToDelete?.previousContractId ?? null },
});
return prisma.contract.delete({ where: { id } });
}
async function createFollowUpContract(previousContractId) {
@@ -419,6 +441,14 @@ async function createFollowUpContract(previousContractId) {
if (!previousContract) {
throw new Error('Vorgängervertrag nicht gefunden');
}
// Prüfen ob bereits ein Folgevertrag existiert
const existingFollowUp = await prisma.contract.findFirst({
where: { previousContractId },
select: { id: true, contractNumber: true },
});
if (existingFollowUp) {
throw new Error(`Es existiert bereits ein Folgevertrag: ${existingFollowUp.contractNumber}`);
}
// Copy data but exclude provider credentials and some fields
const newContractData = {
customerId: previousContract.customerId,
@@ -441,6 +471,7 @@ async function createFollowUpContract(previousContractId) {
annualConsumption: previousContract.energyDetails.annualConsumption ?? undefined,
basePrice: previousContract.energyDetails.basePrice ?? undefined,
unitPrice: previousContract.energyDetails.unitPrice ?? undefined,
bonus: previousContract.energyDetails.bonus ?? undefined,
previousProviderName: previousContract.providerName ?? undefined,
previousCustomerNumber: previousContract.customerNumberAtProvider ?? undefined,
};
File diff suppressed because one or more lines are too long
+2
View File
@@ -128,10 +128,12 @@ export declare function getCustomerById(id: number): Promise<({
createdAt: Date;
updatedAt: Date;
notes: string | null;
hasMailbox: boolean;
platform: string | null;
isProvisioned: boolean;
provisionedAt: Date | null;
provisionError: string | null;
emailPasswordEncrypted: string | null;
}[];
contracts: ({
address: {
File diff suppressed because one or more lines are too long
+4
View File
@@ -108,6 +108,10 @@ async function getCustomerById(id) {
},
stressfreiEmails: { orderBy: { isActive: 'desc' } },
contracts: {
where: {
// Deaktivierte Verträge ausblenden
status: { not: client_1.ContractStatus.DEACTIVATED },
},
orderBy: [{ startDate: 'desc' }, { createdAt: 'desc' }],
include: {
address: true,
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
import { EmailExistsResult, EmailOperationResult } from './types.js';
import { EmailExistsResult, EmailOperationResult, MailEncryption } from './types.js';
export declare function getAllProviderConfigs(): Promise<{
id: number;
isActive: boolean;
@@ -13,6 +13,13 @@ export declare function getAllProviderConfigs(): Promise<{
passwordEncrypted: string | null;
domain: string;
defaultForwardEmail: string | null;
imapServer: string | null;
imapPort: number | null;
smtpServer: string | null;
smtpPort: number | null;
imapEncryption: import(".prisma/client").$Enums.MailEncryption;
smtpEncryption: import(".prisma/client").$Enums.MailEncryption;
allowSelfSignedCerts: boolean;
}[]>;
export declare function getProviderConfigById(id: number): Promise<{
id: number;
@@ -28,6 +35,13 @@ export declare function getProviderConfigById(id: number): Promise<{
passwordEncrypted: string | null;
domain: string;
defaultForwardEmail: string | null;
imapServer: string | null;
imapPort: number | null;
smtpServer: string | null;
smtpPort: number | null;
imapEncryption: import(".prisma/client").$Enums.MailEncryption;
smtpEncryption: import(".prisma/client").$Enums.MailEncryption;
allowSelfSignedCerts: boolean;
} | null>;
export declare function getDefaultProviderConfig(): Promise<{
id: number;
@@ -43,6 +57,13 @@ export declare function getDefaultProviderConfig(): Promise<{
passwordEncrypted: string | null;
domain: string;
defaultForwardEmail: string | null;
imapServer: string | null;
imapPort: number | null;
smtpServer: string | null;
smtpPort: number | null;
imapEncryption: import(".prisma/client").$Enums.MailEncryption;
smtpEncryption: import(".prisma/client").$Enums.MailEncryption;
allowSelfSignedCerts: boolean;
} | null>;
export declare function getActiveProviderConfig(): Promise<{
id: number;
@@ -58,6 +79,13 @@ export declare function getActiveProviderConfig(): Promise<{
passwordEncrypted: string | null;
domain: string;
defaultForwardEmail: string | null;
imapServer: string | null;
imapPort: number | null;
smtpServer: string | null;
smtpPort: number | null;
imapEncryption: import(".prisma/client").$Enums.MailEncryption;
smtpEncryption: import(".prisma/client").$Enums.MailEncryption;
allowSelfSignedCerts: boolean;
} | null>;
export interface CreateProviderConfigData {
name: string;
@@ -68,6 +96,9 @@ export interface CreateProviderConfigData {
password?: string;
domain: string;
defaultForwardEmail?: string;
imapEncryption?: MailEncryption;
smtpEncryption?: MailEncryption;
allowSelfSignedCerts?: boolean;
isActive?: boolean;
isDefault?: boolean;
}
@@ -85,6 +116,13 @@ export declare function createProviderConfig(data: CreateProviderConfigData): Pr
passwordEncrypted: string | null;
domain: string;
defaultForwardEmail: string | null;
imapServer: string | null;
imapPort: number | null;
smtpServer: string | null;
smtpPort: number | null;
imapEncryption: import(".prisma/client").$Enums.MailEncryption;
smtpEncryption: import(".prisma/client").$Enums.MailEncryption;
allowSelfSignedCerts: boolean;
}>;
export declare function updateProviderConfig(id: number, data: Partial<CreateProviderConfigData>): Promise<{
id: number;
@@ -100,6 +138,13 @@ export declare function updateProviderConfig(id: number, data: Partial<CreatePro
passwordEncrypted: string | null;
domain: string;
defaultForwardEmail: string | null;
imapServer: string | null;
imapPort: number | null;
smtpServer: string | null;
smtpPort: number | null;
imapEncryption: import(".prisma/client").$Enums.MailEncryption;
smtpEncryption: import(".prisma/client").$Enums.MailEncryption;
allowSelfSignedCerts: boolean;
}>;
export declare function deleteProviderConfig(id: number): Promise<{
id: number;
@@ -115,9 +160,32 @@ export declare function deleteProviderConfig(id: number): Promise<{
passwordEncrypted: string | null;
domain: string;
defaultForwardEmail: string | null;
imapServer: string | null;
imapPort: number | null;
smtpServer: string | null;
smtpPort: number | null;
imapEncryption: import(".prisma/client").$Enums.MailEncryption;
smtpEncryption: import(".prisma/client").$Enums.MailEncryption;
allowSelfSignedCerts: boolean;
}>;
export declare function checkEmailExists(localPart: string): Promise<EmailExistsResult>;
export declare function provisionEmail(localPart: string, customerEmail: string): Promise<EmailOperationResult>;
export declare function provisionEmailWithMailbox(localPart: string, customerEmail: string, password: string): Promise<EmailOperationResult & {
email?: string;
}>;
export declare function enableMailboxForExistingEmail(localPart: string, password: string): Promise<EmailOperationResult>;
export declare function updateMailboxPassword(localPart: string, password: string): Promise<EmailOperationResult>;
export interface ImapSmtpSettings {
imapServer: string;
imapPort: number;
imapEncryption: MailEncryption;
smtpServer: string;
smtpPort: number;
smtpEncryption: MailEncryption;
allowSelfSignedCerts: boolean;
domain: string;
}
export declare function getImapSmtpSettings(): Promise<ImapSmtpSettings | null>;
export declare function deprovisionEmail(localPart: string): Promise<EmailOperationResult>;
export declare function renameProvisionedEmail(oldLocalPart: string, newLocalPart: string): Promise<EmailOperationResult>;
export declare function getProviderDomain(): Promise<string | null>;
@@ -1 +1 @@
{"version":3,"file":"emailProviderService.d.ts","sourceRoot":"","sources":["../../../src/services/emailProvider/emailProviderService.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,iBAAiB,EACjB,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAuBpB,wBAAsB,qBAAqB;;;;;;;;;;;;;;KAI1C;AAED,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;UAIrD;AAED,wBAAsB,wBAAwB;;;;;;;;;;;;;;UAI7C;AAED,wBAAsB,uBAAuB;;;;;;;;;;;;;;UAQ5C;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,aAAa,CAAC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,wBAAwB;;;;;;;;;;;;;;GA2BxE;AAED,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,OAAO,CAAC,wBAAwB,CAAC;;;;;;;;;;;;;;GAsCxC;AAED,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;GAIpD;AAwCD,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAQpF;AAGD,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,oBAAoB,CAAC,CAoC/B;AAGD,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAWvF;AAGD,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,oBAAoB,CAAC,CAW/B;AAGD,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGhE;AA8DD,wBAAsB,sBAAsB,CAAC,OAAO,CAAC,EAAE;IACrD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE;QACT,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,aAAa,CAAC;QACzC,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA6BhC"}
{"version":3,"file":"emailProviderService.d.ts","sourceRoot":"","sources":["../../../src/services/emailProvider/emailProviderService.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,10 @@ exports.updateProviderConfig = updateProviderConfig;
exports.deleteProviderConfig = deleteProviderConfig;
exports.checkEmailExists = checkEmailExists;
exports.provisionEmail = provisionEmail;
exports.provisionEmailWithMailbox = provisionEmailWithMailbox;
exports.enableMailboxForExistingEmail = enableMailboxForExistingEmail;
exports.updateMailboxPassword = updateMailboxPassword;
exports.getImapSmtpSettings = getImapSmtpSettings;
exports.deprovisionEmail = deprovisionEmail;
exports.renameProvisionedEmail = renameProvisionedEmail;
exports.getProviderDomain = getProviderDomain;
@@ -79,6 +83,9 @@ async function createProviderConfig(data) {
passwordEncrypted,
domain: data.domain,
defaultForwardEmail: data.defaultForwardEmail || null,
imapEncryption: data.imapEncryption ?? 'SSL',
smtpEncryption: data.smtpEncryption ?? 'SSL',
allowSelfSignedCerts: data.allowSelfSignedCerts ?? false,
isActive: data.isActive ?? true,
isDefault: data.isDefault ?? false,
},
@@ -107,6 +114,12 @@ async function updateProviderConfig(id, data) {
updateData.domain = data.domain;
if (data.defaultForwardEmail !== undefined)
updateData.defaultForwardEmail = data.defaultForwardEmail || null;
if (data.imapEncryption !== undefined)
updateData.imapEncryption = data.imapEncryption;
if (data.smtpEncryption !== undefined)
updateData.smtpEncryption = data.smtpEncryption;
if (data.allowSelfSignedCerts !== undefined)
updateData.allowSelfSignedCerts = data.allowSelfSignedCerts;
if (data.isActive !== undefined)
updateData.isActive = data.isActive;
if (data.isDefault !== undefined)
@@ -159,6 +172,13 @@ async function getProviderInstance() {
password,
domain: dbConfig.domain,
defaultForwardEmail: dbConfig.defaultForwardEmail || undefined,
imapServer: dbConfig.imapServer || undefined,
imapPort: dbConfig.imapPort || undefined,
smtpServer: dbConfig.smtpServer || undefined,
smtpPort: dbConfig.smtpPort || undefined,
imapEncryption: dbConfig.imapEncryption,
smtpEncryption: dbConfig.smtpEncryption,
allowSelfSignedCerts: dbConfig.allowSelfSignedCerts,
isActive: dbConfig.isActive,
isDefault: dbConfig.isDefault,
};
@@ -209,6 +229,136 @@ async function provisionEmail(localPart, customerEmail) {
};
}
}
// E-Mail mit echter Mailbox erstellen (IMAP/SMTP-Zugang)
async function provisionEmailWithMailbox(localPart, customerEmail, password) {
try {
const provider = await getProviderInstance();
const config = await getActiveProviderConfig();
// Weiterleitungsziele zusammenstellen
const forwardTargets = [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`,
email: exists.email,
};
}
// Mit Mailbox erstellen
const result = await provider.createEmailWithMailbox({
localPart,
forwardTargets,
password,
});
return result;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
return {
success: false,
error: errorMessage,
};
}
}
// Mailbox für existierende E-Mail-Weiterleitung aktivieren
async function enableMailboxForExistingEmail(localPart, password) {
try {
const provider = await getProviderInstance();
const result = await provider.enableMailboxForExisting({
localPart,
password,
});
return result;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
return {
success: false,
error: errorMessage,
};
}
}
// Mailbox-Passwort beim Provider aktualisieren
async function updateMailboxPassword(localPart, password) {
try {
const provider = await getProviderInstance();
const result = await provider.updateMailboxPassword({
localPart,
password,
});
return result;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
return {
success: false,
error: errorMessage,
};
}
}
async function getImapSmtpSettings() {
const config = await getActiveProviderConfig();
if (!config)
return null;
// Default-Server: Hostname aus der apiUrl extrahieren (z.B. rs001871.fastrootserver.de aus https://rs001871.fastrootserver.de:8443)
// Der Plesk-Server ist gleichzeitig der Mail-Server
let defaultServer;
try {
const url = new URL(config.apiUrl);
defaultServer = url.hostname;
}
catch {
// Fallback falls apiUrl ungültig
defaultServer = `mail.${config.domain}`;
}
// Verschlüsselungs-Einstellungen
const imapEncryption = (config.imapEncryption ?? 'SSL');
const smtpEncryption = (config.smtpEncryption ?? 'SSL');
// Ports basierend auf Verschlüsselung berechnen:
// SSL: IMAP 993, SMTP 465
// STARTTLS: IMAP 143, SMTP 587
// NONE: IMAP 143, SMTP 25
//
// Standard-Ports werden IMMER basierend auf Verschlüsselung berechnet.
// Nur benutzerdefinierte Ports (nicht 993/143/465/587/25) werden aus der DB übernommen.
const getImapPort = (enc, storedPort) => {
const standardPorts = [993, 143];
// Wenn ein nicht-standard Port gespeichert ist, diesen verwenden
if (storedPort && !standardPorts.includes(storedPort)) {
return storedPort;
}
// Sonst basierend auf Verschlüsselung
return enc === 'SSL' ? 993 : 143;
};
const getSmtpPort = (enc, storedPort) => {
const standardPorts = [465, 587, 25];
// Wenn ein nicht-standard Port gespeichert ist, diesen verwenden
if (storedPort && !standardPorts.includes(storedPort)) {
return storedPort;
}
// Sonst basierend auf Verschlüsselung
if (enc === 'SSL')
return 465;
if (enc === 'STARTTLS')
return 587;
return 25; // NONE
};
return {
imapServer: config.imapServer || defaultServer,
imapPort: getImapPort(imapEncryption, config.imapPort),
imapEncryption,
smtpServer: config.smtpServer || defaultServer,
smtpPort: getSmtpPort(smtpEncryption, config.smtpPort),
smtpEncryption,
allowSelfSignedCerts: config.allowSelfSignedCerts ?? false,
domain: config.domain,
};
}
// E-Mail löschen
async function deprovisionEmail(localPart) {
try {
@@ -284,6 +434,13 @@ async function getProviderInstanceById(id) {
password,
domain: dbConfig.domain,
defaultForwardEmail: dbConfig.defaultForwardEmail || undefined,
imapServer: dbConfig.imapServer || undefined,
imapPort: dbConfig.imapPort || undefined,
smtpServer: dbConfig.smtpServer || undefined,
smtpPort: dbConfig.smtpPort || undefined,
imapEncryption: dbConfig.imapEncryption,
smtpEncryption: dbConfig.smtpEncryption,
allowSelfSignedCerts: dbConfig.allowSelfSignedCerts,
isActive: dbConfig.isActive,
isDefault: dbConfig.isDefault,
};
File diff suppressed because one or more lines are too long
+4 -1
View File
@@ -1,4 +1,4 @@
import { IEmailProvider, EmailProviderConfig, EmailExistsResult, EmailOperationResult, CreateEmailParams, RenameEmailParams } from './types.js';
import { IEmailProvider, EmailProviderConfig, EmailExistsResult, EmailOperationResult, CreateEmailParams, CreateEmailWithMailboxParams, CreateEmailWithMailboxResult, EnableMailboxParams, UpdateMailboxPasswordParams, RenameEmailParams } from './types.js';
export declare class PleskEmailProvider implements IEmailProvider {
readonly type = "PLESK";
private config;
@@ -8,6 +8,9 @@ export declare class PleskEmailProvider implements IEmailProvider {
testConnection(): Promise<void>;
emailExists(localPart: string): Promise<EmailExistsResult>;
createEmail(params: CreateEmailParams): Promise<EmailOperationResult>;
createEmailWithMailbox(params: CreateEmailWithMailboxParams): Promise<CreateEmailWithMailboxResult>;
enableMailboxForExisting(params: EnableMailboxParams): Promise<EmailOperationResult>;
updateMailboxPassword(params: UpdateMailboxPasswordParams): Promise<EmailOperationResult>;
deleteEmail(localPart: string): Promise<EmailOperationResult>;
renameEmail(params: RenameEmailParams): Promise<EmailOperationResult>;
updateForwardTargets(localPart: string, targets: string[]): Promise<EmailOperationResult>;
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"pleskProvider.d.ts","sourceRoot":"","sources":["../../../src/services/emailProvider/pleskProvider.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAiBpB,qBAAa,kBAAmB,YAAW,cAAc;IACvD,QAAQ,CAAC,IAAI,WAAW;IACxB,OAAO,CAAC,MAAM,CAAsB;gBAExB,MAAM,EAAE,mBAAmB;IAKvC,OAAO,KAAK,OAAO,GAGlB;YAGa,OAAO;IAsFf,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB/B,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA8C1D,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAuCrE,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAgC7D,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA2CrE,oBAAoB,CACxB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,oBAAoB,CAAC;CAmCjC"}
{"version":3,"file":"pleskProvider.d.ts","sourceRoot":"","sources":["../../../src/services/emailProvider/pleskProvider.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,4BAA4B,EAC5B,4BAA4B,EAC5B,mBAAmB,EACnB,2BAA2B,EAC3B,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAiBpB,qBAAa,kBAAmB,YAAW,cAAc;IACvD,QAAQ,CAAC,IAAI,WAAW;IACxB,OAAO,CAAC,MAAM,CAAsB;gBAExB,MAAM,EAAE,mBAAmB;IAKvC,OAAO,KAAK,OAAO,GAGlB;YAGa,OAAO;IAsFf,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB/B,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAyD1D,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAwCrE,sBAAsB,CAAC,MAAM,EAAE,4BAA4B,GAAG,OAAO,CAAC,4BAA4B,CAAC;IA2CnG,wBAAwB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAwCpF,qBAAqB,CAAC,MAAM,EAAE,2BAA2B,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAsCzF,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAgC7D,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA2CrE,oBAAoB,CACxB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,oBAAoB,CAAC;CAoCjC"}
+125 -2
View File
@@ -137,9 +137,18 @@ class PleskEmailProvider {
}
// stdout sollte die Mail-Infos enthalten
const exists = result.stdout?.toLowerCase().includes(localPart.toLowerCase());
// Mailbox-Status aus stdout parsen (Format: "Mailbox: true" oder "Mailbox: false")
let hasMailbox;
if (exists && result.stdout) {
const mailboxMatch = result.stdout.match(/Mailbox:\s*(true|false)/i);
if (mailboxMatch) {
hasMailbox = mailboxMatch[1].toLowerCase() === 'true';
}
}
return {
exists,
email: exists ? email : undefined,
hasMailbox,
};
}
catch (error) {
@@ -169,11 +178,12 @@ class PleskEmailProvider {
}
// Plesk CLI API: Mail-Account mit Weiterleitung erstellen
// Verwendet den CLI-Wrapper unter /api/v2/cli/mail/call
// Format für -forwarding-addresses: "add:email1,email2" oder "set:email1,email2"
await this.request('POST', '/api/v2/cli/mail/call', {
params: [
'--create', email,
'-forwarding', 'true',
'-forwarding-addresses', forwardTargets.join(','),
'-forwarding-addresses', `add:${forwardTargets.join(',')}`,
'-mailbox', 'false',
],
});
@@ -191,6 +201,118 @@ class PleskEmailProvider {
};
}
}
async createEmailWithMailbox(params) {
const { localPart, forwardTargets, password } = params;
const email = `${localPart}@${this.config.domain}`;
try {
// Prüfen ob schon existiert
const exists = await this.emailExists(localPart);
if (exists.exists) {
return {
success: false,
error: `E-Mail ${email} existiert bereits`,
};
}
// Plesk CLI API: Mail-Account mit echter Mailbox erstellen
// -mailbox true: Echte Mailbox (IMAP/SMTP-Zugang)
// -passwd: Passwort für die Mailbox
// -forwarding true: Zusätzlich Weiterleitung aktivieren
await this.request('POST', '/api/v2/cli/mail/call', {
params: [
'--create', email,
'-mailbox', 'true',
'-passwd', password,
'-forwarding', 'true',
'-forwarding-addresses', `add:${forwardTargets.join(',')}`,
],
});
return {
success: true,
message: `E-Mail ${email} mit Mailbox erfolgreich erstellt`,
email,
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
console.error('Plesk createEmailWithMailbox error:', error);
return {
success: false,
error: `Fehler beim Erstellen der E-Mail mit Mailbox: ${errorMessage}`,
};
}
}
async enableMailboxForExisting(params) {
const { localPart, password } = params;
const email = `${localPart}@${this.config.domain}`;
try {
// Prüfen ob E-Mail existiert
const exists = await this.emailExists(localPart);
if (!exists.exists) {
return {
success: false,
error: `E-Mail ${email} nicht gefunden`,
};
}
// Plesk CLI API: Mailbox für existierende E-Mail aktivieren
// --update: Existierende E-Mail aktualisieren
// -mailbox true: Mailbox aktivieren
// -passwd: Passwort für die Mailbox setzen
await this.request('POST', '/api/v2/cli/mail/call', {
params: [
'--update', email,
'-mailbox', 'true',
'-passwd', password,
],
});
return {
success: true,
message: `Mailbox für ${email} erfolgreich aktiviert`,
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
console.error('Plesk enableMailboxForExisting error:', error);
return {
success: false,
error: `Fehler beim Aktivieren der Mailbox: ${errorMessage}`,
};
}
}
async updateMailboxPassword(params) {
const { localPart, password } = params;
const email = `${localPart}@${this.config.domain}`;
try {
// Prüfen ob E-Mail existiert
const exists = await this.emailExists(localPart);
if (!exists.exists) {
return {
success: false,
error: `E-Mail ${email} nicht gefunden`,
};
}
// Plesk CLI API: Passwort für existierende E-Mail aktualisieren
// --update: Existierende E-Mail aktualisieren
// -passwd: Neues Passwort setzen
await this.request('POST', '/api/v2/cli/mail/call', {
params: [
'--update', email,
'-passwd', password,
],
});
return {
success: true,
message: `Passwort für ${email} erfolgreich aktualisiert`,
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
console.error('Plesk updateMailboxPassword error:', error);
return {
success: false,
error: `Fehler beim Aktualisieren des Passworts: ${errorMessage}`,
};
}
}
async deleteEmail(localPart) {
const email = `${localPart}@${this.config.domain}`;
try {
@@ -271,11 +393,12 @@ class PleskEmailProvider {
};
}
// Plesk CLI API: Weiterleitungsziele aktualisieren
// Format für -forwarding-addresses: "set:email1,email2" ersetzt alle Adressen
await this.request('POST', '/api/v2/cli/mail/call', {
params: [
'--update', email,
'-forwarding', 'true',
'-forwarding-addresses', targets.join(','),
'-forwarding-addresses', `set:${targets.join(',')}`,
],
});
return {
File diff suppressed because one or more lines are too long
+28
View File
@@ -1,3 +1,4 @@
export type MailEncryption = 'SSL' | 'STARTTLS' | 'NONE';
export interface EmailForwardTarget {
email: string;
}
@@ -5,6 +6,22 @@ export interface CreateEmailParams {
localPart: string;
forwardTargets: string[];
}
export interface CreateEmailWithMailboxParams {
localPart: string;
forwardTargets: string[];
password: string;
}
export interface CreateEmailWithMailboxResult extends EmailOperationResult {
email?: string;
}
export interface EnableMailboxParams {
localPart: string;
password: string;
}
export interface UpdateMailboxPasswordParams {
localPart: string;
password: string;
}
export interface RenameEmailParams {
oldLocalPart: string;
newLocalPart: string;
@@ -12,6 +29,7 @@ export interface RenameEmailParams {
export interface EmailExistsResult {
exists: boolean;
email?: string;
hasMailbox?: boolean;
}
export interface EmailOperationResult {
success: boolean;
@@ -23,6 +41,9 @@ export interface IEmailProvider {
testConnection(): Promise<void>;
emailExists(localPart: string): Promise<EmailExistsResult>;
createEmail(params: CreateEmailParams): Promise<EmailOperationResult>;
createEmailWithMailbox(params: CreateEmailWithMailboxParams): Promise<CreateEmailWithMailboxResult>;
enableMailboxForExisting(params: EnableMailboxParams): Promise<EmailOperationResult>;
updateMailboxPassword(params: UpdateMailboxPasswordParams): Promise<EmailOperationResult>;
deleteEmail(localPart: string): Promise<EmailOperationResult>;
renameEmail(params: RenameEmailParams): Promise<EmailOperationResult>;
updateForwardTargets(localPart: string, targets: string[]): Promise<EmailOperationResult>;
@@ -37,6 +58,13 @@ export interface EmailProviderConfig {
password?: string;
domain: string;
defaultForwardEmail?: string;
imapServer?: string;
imapPort?: number;
smtpServer?: string;
smtpPort?: number;
imapEncryption?: MailEncryption;
smtpEncryption?: MailEncryption;
allowSelfSignedCerts?: boolean;
isActive: boolean;
isDefault: boolean;
}
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/emailProvider/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAGD,MAAM,WAAW,cAAc;IAE7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAGtB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAGhC,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAG3D,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAGtE,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAG9D,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAGtE,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAC3F;AAGD,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,aAAa,CAAC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;CACpB"}
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/emailProvider/types.ts"],"names":[],"mappings":"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"}
+56 -6
View File
@@ -6,10 +6,21 @@ export declare function getEmailsByCustomerId(customerId: number, includeInactiv
createdAt: Date;
updatedAt: Date;
notes: string | null;
hasMailbox: boolean;
platform: string | null;
isProvisioned: boolean;
provisionedAt: Date | null;
provisionError: string | null;
emailPasswordEncrypted: string | null;
}[]>;
export declare function getEmailsWithMailboxByCustomerId(customerId: number): Promise<{
id: number;
email: string;
notes: string | null;
_count: {
cachedEmails: number;
};
hasMailbox: boolean;
}[]>;
export declare function getEmailById(id: number): Promise<{
id: number;
@@ -19,17 +30,14 @@ export declare function getEmailById(id: number): Promise<{
createdAt: Date;
updatedAt: Date;
notes: string | null;
hasMailbox: boolean;
platform: string | null;
isProvisioned: boolean;
provisionedAt: Date | null;
provisionError: string | null;
emailPasswordEncrypted: string | null;
} | null>;
export declare function createEmail(data: {
customerId: number;
email: string;
platform?: string;
notes?: string;
}): Promise<{
export declare function getEmailWithMailboxById(id: number): Promise<{
id: number;
email: string;
customerId: number;
@@ -37,10 +45,32 @@ export declare function createEmail(data: {
createdAt: Date;
updatedAt: Date;
notes: string | null;
hasMailbox: boolean;
platform: string | null;
emailPasswordEncrypted: string | null;
} | null>;
export interface CreateEmailData {
customerId: number;
email: string;
platform?: string;
notes?: string;
provisionAtProvider?: boolean;
createMailbox?: boolean;
}
export declare function createEmail(data: CreateEmailData): Promise<{
id: number;
email: string;
customerId: number;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
notes: string | null;
hasMailbox: boolean;
platform: string | null;
isProvisioned: boolean;
provisionedAt: Date | null;
provisionError: string | null;
emailPasswordEncrypted: string | null;
}>;
export declare function updateEmail(id: number, data: {
email?: string;
@@ -55,10 +85,12 @@ export declare function updateEmail(id: number, data: {
createdAt: Date;
updatedAt: Date;
notes: string | null;
hasMailbox: boolean;
platform: string | null;
isProvisioned: boolean;
provisionedAt: Date | null;
provisionError: string | null;
emailPasswordEncrypted: string | null;
}>;
export declare function deleteEmail(id: number): Promise<{
id: number;
@@ -68,9 +100,27 @@ export declare function deleteEmail(id: number): Promise<{
createdAt: Date;
updatedAt: Date;
notes: string | null;
hasMailbox: boolean;
platform: string | null;
isProvisioned: boolean;
provisionedAt: Date | null;
provisionError: string | null;
emailPasswordEncrypted: string | null;
}>;
export declare function enableMailbox(id: number): Promise<{
success: boolean;
error?: string;
}>;
export declare function syncMailboxStatus(id: number): Promise<{
success: boolean;
hasMailbox?: boolean;
wasUpdated?: boolean;
error?: string;
}>;
export declare function getDecryptedPassword(id: number): Promise<string | null>;
export declare function resetMailboxPassword(id: number): Promise<{
success: boolean;
password?: string;
error?: string;
}>;
//# sourceMappingURL=stressfreiEmail.service.d.ts.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"stressfreiEmail.service.d.ts","sourceRoot":"","sources":["../../src/services/stressfreiEmail.service.ts"],"names":[],"mappings":"AAIA,wBAAsB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,eAAe,UAAQ;;;;;;;;;;;;KAStF;AAED,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;UAI5C;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;;;;;;;;;;;;GAOA;AAED,wBAAsB,WAAW,CAC/B,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IACJ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;;;;;;;;;;;;GAMF;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;GAE3C"}
{"version":3,"file":"stressfreiEmail.service.d.ts","sourceRoot":"","sources":["../../src/services/stressfreiEmail.service.ts"],"names":[],"mappings":"AAcA,wBAAsB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,eAAe,UAAQ;;;;;;;;;;;;;;KAStF;AAGD,wBAAsB,gCAAgC,CAAC,UAAU,EAAE,MAAM;;;;;;;;KAoBxE;AAED,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;UAI5C;AAGD,wBAAsB,uBAAuB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;UAgBvD;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,eAAe;;;;;;;;;;;;;;GAuDtD;AAED,wBAAsB,WAAW,CAC/B,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IACJ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;;;;;;;;;;;;;;GAMF;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;GAE3C;AAGD,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAmC7F;AAGD,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3D,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAC,CAgCD;AAGD,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgB7E;AAGD,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAiCvH"}
+191 -1
View File
@@ -1,11 +1,20 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getEmailsByCustomerId = getEmailsByCustomerId;
exports.getEmailsWithMailboxByCustomerId = getEmailsWithMailboxByCustomerId;
exports.getEmailById = getEmailById;
exports.getEmailWithMailboxById = getEmailWithMailboxById;
exports.createEmail = createEmail;
exports.updateEmail = updateEmail;
exports.deleteEmail = deleteEmail;
exports.enableMailbox = enableMailbox;
exports.syncMailboxStatus = syncMailboxStatus;
exports.getDecryptedPassword = getDecryptedPassword;
exports.resetMailboxPassword = resetMailboxPassword;
const client_1 = require("@prisma/client");
const encryption_js_1 = require("../utils/encryption.js");
const emailProviderService_js_1 = require("./emailProvider/emailProviderService.js");
const passwordGenerator_js_1 = require("../utils/passwordGenerator.js");
const prisma = new client_1.PrismaClient();
async function getEmailsByCustomerId(customerId, includeInactive = false) {
const where = { customerId };
@@ -17,16 +26,96 @@ async function getEmailsByCustomerId(customerId, includeInactive = false) {
orderBy: { createdAt: 'desc' },
});
}
// Mit Mailbox-Status für E-Mail-Client
async function getEmailsWithMailboxByCustomerId(customerId) {
return prisma.stressfreiEmail.findMany({
where: {
customerId,
isActive: true,
hasMailbox: true,
},
select: {
id: true,
email: true,
notes: true,
hasMailbox: true,
_count: {
select: {
cachedEmails: true,
},
},
},
orderBy: { email: 'asc' },
});
}
async function getEmailById(id) {
return prisma.stressfreiEmail.findUnique({
where: { id },
});
}
// E-Mail mit Mailbox-Status laden
async function getEmailWithMailboxById(id) {
return prisma.stressfreiEmail.findUnique({
where: { id },
select: {
id: true,
customerId: true,
email: true,
platform: true,
notes: true,
isActive: true,
hasMailbox: true,
emailPasswordEncrypted: true,
createdAt: true,
updatedAt: true,
},
});
}
async function createEmail(data) {
const { provisionAtProvider, createMailbox, ...emailData } = data;
// Falls beim Provider anlegen gewünscht
if (provisionAtProvider) {
// Kunde laden für Weiterleitung
const customer = await prisma.customer.findUnique({
where: { id: data.customerId },
select: { email: true },
});
if (!customer?.email) {
throw new Error('Kunde hat keine E-Mail-Adresse für Weiterleitung');
}
// LocalPart extrahieren
const localPart = data.email.split('@')[0];
if (createMailbox) {
// Mit echter Mailbox anlegen
const password = (0, passwordGenerator_js_1.generateSecurePassword)();
const result = await (0, emailProviderService_js_1.provisionEmailWithMailbox)(localPart, customer.email, password);
if (!result.success) {
throw new Error(result.error || 'Fehler beim Anlegen der Mailbox');
}
// Passwort verschlüsseln und speichern
const passwordEncrypted = (0, encryption_js_1.encrypt)(password);
return prisma.stressfreiEmail.create({
data: {
...emailData,
isActive: true,
hasMailbox: true,
emailPasswordEncrypted: passwordEncrypted,
},
});
}
else {
// Nur Weiterleitung anlegen
const result = await (0, emailProviderService_js_1.provisionEmail)(localPart, customer.email);
if (!result.success && !result.message?.includes('existiert bereits')) {
throw new Error(result.error || 'Fehler beim Anlegen der E-Mail');
}
}
}
return prisma.stressfreiEmail.create({
data: {
...data,
...emailData,
isActive: true,
hasMailbox: createMailbox || false,
},
});
}
@@ -39,4 +128,105 @@ async function updateEmail(id, data) {
async function deleteEmail(id) {
return prisma.stressfreiEmail.delete({ where: { id } });
}
// Mailbox nachträglich aktivieren (für existierende E-Mail-Weiterleitung)
async function enableMailbox(id) {
const stressfreiEmail = await prisma.stressfreiEmail.findUnique({
where: { id },
});
if (!stressfreiEmail) {
return { success: false, error: 'StressfreiEmail nicht gefunden' };
}
if (stressfreiEmail.hasMailbox) {
return { success: false, error: 'Mailbox ist bereits aktiviert' };
}
const localPart = stressfreiEmail.email.split('@')[0];
const password = (0, passwordGenerator_js_1.generateSecurePassword)();
// Mailbox für existierende E-Mail aktivieren (nicht neu erstellen!)
const result = await (0, emailProviderService_js_1.enableMailboxForExistingEmail)(localPart, password);
if (!result.success) {
return { success: false, error: result.error || 'Fehler beim Aktivieren der Mailbox' };
}
// Passwort verschlüsseln und speichern
const passwordEncrypted = (0, encryption_js_1.encrypt)(password);
await prisma.stressfreiEmail.update({
where: { id },
data: {
hasMailbox: true,
emailPasswordEncrypted: passwordEncrypted,
},
});
return { success: true };
}
// Mailbox-Status mit Provider synchronisieren
async function syncMailboxStatus(id) {
const stressfreiEmail = await prisma.stressfreiEmail.findUnique({
where: { id },
select: { email: true, hasMailbox: true },
});
if (!stressfreiEmail) {
return { success: false, error: 'StressfreiEmail nicht gefunden' };
}
const localPart = stressfreiEmail.email.split('@')[0];
// Provider-Status prüfen
const providerStatus = await (0, emailProviderService_js_1.checkEmailExists)(localPart);
if (!providerStatus.exists) {
return { success: true, hasMailbox: false, wasUpdated: false };
}
const providerHasMailbox = providerStatus.hasMailbox === true;
// DB aktualisieren wenn Status abweicht
if (stressfreiEmail.hasMailbox !== providerHasMailbox) {
await prisma.stressfreiEmail.update({
where: { id },
data: { hasMailbox: providerHasMailbox },
});
console.log(`Mailbox-Status für ${stressfreiEmail.email} aktualisiert: ${stressfreiEmail.hasMailbox} -> ${providerHasMailbox}`);
return { success: true, hasMailbox: providerHasMailbox, wasUpdated: true };
}
return { success: true, hasMailbox: providerHasMailbox, wasUpdated: false };
}
// Passwort für IMAP/SMTP-Zugang entschlüsseln (nur für autorisierte Nutzung)
async function getDecryptedPassword(id) {
const stressfreiEmail = await prisma.stressfreiEmail.findUnique({
where: { id },
select: { emailPasswordEncrypted: true },
});
if (!stressfreiEmail?.emailPasswordEncrypted) {
return null;
}
try {
return (0, encryption_js_1.decrypt)(stressfreiEmail.emailPasswordEncrypted);
}
catch {
console.error('Fehler beim Entschlüsseln des Passworts');
return null;
}
}
// Passwort neu generieren und beim Provider setzen
async function resetMailboxPassword(id) {
const stressfreiEmail = await prisma.stressfreiEmail.findUnique({
where: { id },
select: { email: true, hasMailbox: true },
});
if (!stressfreiEmail) {
return { success: false, error: 'StressfreiEmail nicht gefunden' };
}
if (!stressfreiEmail.hasMailbox) {
return { success: false, error: 'Keine Mailbox für diese E-Mail-Adresse' };
}
// Neues Passwort generieren
const newPassword = (0, passwordGenerator_js_1.generateSecurePassword)();
const localPart = stressfreiEmail.email.split('@')[0];
// Passwort beim Provider ändern
const providerResult = await (0, emailProviderService_js_1.updateMailboxPassword)(localPart, newPassword);
if (!providerResult.success) {
return { success: false, error: providerResult.error || 'Fehler beim Aktualisieren des Passworts beim Provider' };
}
// Passwort verschlüsseln und lokal speichern
const passwordEncrypted = (0, encryption_js_1.encrypt)(newPassword);
await prisma.stressfreiEmail.update({
where: { id },
data: { emailPasswordEncrypted: passwordEncrypted },
});
return { success: true, password: newPassword };
}
//# sourceMappingURL=stressfreiEmail.service.js.map
File diff suppressed because one or more lines are too long
+2
View File
@@ -71,6 +71,7 @@ export declare function createUser(data: {
lastName: string;
roleIds: number[];
customerId?: number;
hasDeveloperAccess?: boolean;
}): Promise<{
id: number;
email: string;
@@ -137,6 +138,7 @@ export declare function deleteUser(id: number): Promise<{
firstName: string;
lastName: string;
isActive: boolean;
tokenInvalidatedAt: Date | null;
createdAt: Date;
updatedAt: Date;
}>;
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"user.service.d.ts","sourceRoot":"","sources":["../../src/services/user.service.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqErD;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA0C3C;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;;;;;;;;;;;;;;;;;;;GA0BA;AAED,wBAAsB,UAAU,CAC9B,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IACJ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAsHF;AA0DD,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;GA6D1C;AAGD,wBAAsB,WAAW;;;;;;;;;;;;;;;;;;;;MAYhC;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;WAS3C;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;;;;;;;;;;;;;;;;;GAeA;AAED,wBAAsB,UAAU,CAC9B,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IACJ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;;;;;;;;;;;;;;;;;WAiBF;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM;;;;;;GAU1C;AAGD,wBAAsB,iBAAiB;;;;KAItC"}
{"version":3,"file":"user.service.d.ts","sourceRoot":"","sources":["../../src/services/user.service.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqErD;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA0C3C;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;;;;;;;;;;;;;;;;;;;GAiCA;AAED,wBAAsB,UAAU,CAC9B,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IACJ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAwIF;AAoED,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;GA6D1C;AAGD,wBAAsB,WAAW;;;;;;;;;;;;;;;;;;;;MAYhC;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;WAS3C;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;;;;;;;;;;;;;;;;;GAeA;AAED,wBAAsB,UAAU,CAC9B,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IACJ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;;;;;;;;;;;;;;;;;WAiBF;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM;;;;;;GAU1C;AAGD,wBAAsB,iBAAiB;;;;KAItC"}
+35 -3
View File
@@ -122,7 +122,7 @@ async function getUserById(id) {
}
async function createUser(data) {
const hashedPassword = await bcryptjs_1.default.hash(data.password, 10);
return prisma.user.create({
const user = await prisma.user.create({
data: {
email: data.email,
password: hashedPassword,
@@ -145,6 +145,11 @@ async function createUser(data) {
},
},
});
// Entwicklerzugriff setzen falls aktiviert
if (data.hasDeveloperAccess) {
await setUserDeveloperAccess(user.id, true);
}
return user;
}
async function updateUser(id, data) {
const { roleIds, password, hasDeveloperAccess, ...userData } = data;
@@ -224,10 +229,27 @@ async function updateUser(id, data) {
if (password) {
userData.password = await bcryptjs_1.default.hash(password, 10);
}
// Update user
// Prüfen ob Rollen geändert werden (für Zwangslogout)
let rolesChanged = false;
if (roleIds !== undefined) {
const currentRoles = await prisma.userRole.findMany({
where: { userId: id },
select: { roleId: true },
});
const currentRoleIds = currentRoles.map((r) => r.roleId).sort();
const newRoleIds = [...roleIds].sort();
rolesChanged =
currentRoleIds.length !== newRoleIds.length ||
!currentRoleIds.every((id, i) => id === newRoleIds[i]);
}
// Update user - bei Rollenänderung Token invalidieren
await prisma.user.update({
where: { id },
data: userData,
data: {
...userData,
// Token invalidieren wenn Rollen geändert werden
...(rolesChanged && { tokenInvalidatedAt: new Date() }),
},
});
// Update roles if provided
if (roleIds) {
@@ -281,6 +303,11 @@ async function setUserDeveloperAccess(userId, enabled) {
await prisma.userRole.create({
data: { userId, roleId: developerRole.id },
});
// Token invalidieren bei Rechteänderung
await prisma.user.update({
where: { id: userId },
data: { tokenInvalidatedAt: new Date() },
});
}
else if (!enabled && hasRole) {
// Remove Developer role
@@ -288,6 +315,11 @@ async function setUserDeveloperAccess(userId, enabled) {
await prisma.userRole.delete({
where: { userId_roleId: { userId, roleId: developerRole.id } },
});
// Token invalidieren bei Rechteänderung
await prisma.user.update({
where: { id: userId },
data: { tokenInvalidatedAt: new Date() },
});
}
else {
console.log('No action needed - enabled:', enabled, 'hasRole:', !!hasRole);
File diff suppressed because one or more lines are too long