added backup and email client
This commit is contained in:
@@ -216,16 +216,95 @@ export interface StressfreiEmail {
|
||||
platform?: string;
|
||||
notes?: string;
|
||||
isActive: boolean;
|
||||
hasMailbox: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
// Mailbox-Konto (für Dropdown-Auswahl)
|
||||
export interface MailboxAccount {
|
||||
id: number;
|
||||
email: string;
|
||||
notes?: string;
|
||||
hasMailbox: boolean;
|
||||
_count: {
|
||||
cachedEmails: number;
|
||||
};
|
||||
}
|
||||
|
||||
// Gecachte E-Mail
|
||||
export interface CachedEmail {
|
||||
id: number;
|
||||
stressfreiEmailId: number;
|
||||
folder: 'INBOX' | 'SENT';
|
||||
messageId: string;
|
||||
uid: number;
|
||||
subject?: string;
|
||||
fromAddress: string;
|
||||
fromName?: string;
|
||||
toAddresses: string; // JSON Array
|
||||
ccAddresses?: string; // JSON Array
|
||||
receivedAt: string;
|
||||
textBody?: string;
|
||||
htmlBody?: string;
|
||||
hasAttachments: boolean;
|
||||
attachmentNames?: string; // JSON Array
|
||||
contractId?: number;
|
||||
assignedAt?: string;
|
||||
assignedBy?: number;
|
||||
isAutoAssigned?: boolean; // true = automatisch beim Senden aus Vertrag zugeordnet
|
||||
isRead: boolean;
|
||||
isStarred: boolean;
|
||||
isDeleted: boolean;
|
||||
deletedAt?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
stressfreiEmail?: {
|
||||
id: number;
|
||||
email: string;
|
||||
customerId: number;
|
||||
};
|
||||
contract?: {
|
||||
id: number;
|
||||
contractNumber: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface SyncResult {
|
||||
newEmails: number;
|
||||
totalEmails: number;
|
||||
}
|
||||
|
||||
export interface EmailAttachment {
|
||||
filename: string;
|
||||
content: string; // Base64-kodierter Inhalt
|
||||
contentType?: string; // MIME-Type (z.B. 'application/pdf')
|
||||
}
|
||||
|
||||
export interface SendEmailParams {
|
||||
to: string | string[];
|
||||
cc?: string | string[];
|
||||
subject: string;
|
||||
text?: string;
|
||||
html?: string;
|
||||
inReplyTo?: string;
|
||||
references?: string[];
|
||||
attachments?: EmailAttachment[];
|
||||
contractId?: number; // Vertrag dem die gesendete E-Mail zugeordnet wird
|
||||
}
|
||||
|
||||
export const stressfreiEmailApi = {
|
||||
getByCustomer: async (customerId: number, includeInactive = false) => {
|
||||
const res = await api.get<ApiResponse<StressfreiEmail[]>>(`/customers/${customerId}/stressfrei-emails`, { params: { includeInactive } });
|
||||
return res.data;
|
||||
},
|
||||
create: async (customerId: number, data: { email: string; platform?: string; notes?: string }) => {
|
||||
create: async (customerId: number, data: {
|
||||
email: string;
|
||||
platform?: string;
|
||||
notes?: string;
|
||||
provisionAtProvider?: boolean;
|
||||
createMailbox?: boolean;
|
||||
}) => {
|
||||
const res = await api.post<ApiResponse<StressfreiEmail>>(`/customers/${customerId}/stressfrei-emails`, data);
|
||||
return res.data;
|
||||
},
|
||||
@@ -237,6 +316,150 @@ export const stressfreiEmailApi = {
|
||||
const res = await api.delete<ApiResponse<void>>(`/stressfrei-emails/${id}`);
|
||||
return res.data;
|
||||
},
|
||||
// Mailbox nachträglich aktivieren
|
||||
enableMailbox: async (id: number) => {
|
||||
const res = await api.post<ApiResponse<null>>(`/stressfrei-emails/${id}/enable-mailbox`);
|
||||
return res.data;
|
||||
},
|
||||
// Mailbox-Status mit Provider synchronisieren
|
||||
syncMailboxStatus: async (id: number) => {
|
||||
const res = await api.post<ApiResponse<{ hasMailbox: boolean; wasUpdated: boolean }>>(`/stressfrei-emails/${id}/sync-mailbox-status`);
|
||||
return res.data;
|
||||
},
|
||||
// Mailbox-Zugangsdaten abrufen (IMAP/SMTP)
|
||||
getMailboxCredentials: async (id: number) => {
|
||||
const res = await api.get<ApiResponse<{
|
||||
email: string;
|
||||
password: string;
|
||||
imap: { server: string; port: number; encryption: string } | null;
|
||||
smtp: { server: string; port: number; encryption: string } | null;
|
||||
}>>(`/stressfrei-emails/${id}/credentials`);
|
||||
return res.data;
|
||||
},
|
||||
// Passwort zurücksetzen (generiert neues Passwort beim Provider)
|
||||
resetPassword: async (id: number) => {
|
||||
const res = await api.post<ApiResponse<{ password: string }>>(`/stressfrei-emails/${id}/reset-password`);
|
||||
return res.data;
|
||||
},
|
||||
// E-Mails synchronisieren
|
||||
syncEmails: async (id: number, fullSync = false) => {
|
||||
const res = await api.post<ApiResponse<SyncResult>>(`/stressfrei-emails/${id}/sync`, {}, { params: { full: fullSync } });
|
||||
return res.data;
|
||||
},
|
||||
// E-Mail senden
|
||||
sendEmail: async (id: number, params: SendEmailParams) => {
|
||||
const res = await api.post<ApiResponse<{ messageId: string }>>(`/stressfrei-emails/${id}/send`, params);
|
||||
return res.data;
|
||||
},
|
||||
// Ordner-Anzahlen abrufen (total und ungelesen pro Ordner)
|
||||
getFolderCounts: async (id: number) => {
|
||||
const res = await api.get<ApiResponse<{
|
||||
inbox: number;
|
||||
inboxUnread: number;
|
||||
sent: number;
|
||||
sentUnread: number;
|
||||
trash: number;
|
||||
trashUnread: number;
|
||||
}>>(`/stressfrei-emails/${id}/folder-counts`);
|
||||
return res.data;
|
||||
},
|
||||
};
|
||||
|
||||
// Cached Email API (E-Mail-Client)
|
||||
export const cachedEmailApi = {
|
||||
// E-Mails für Kunden abrufen
|
||||
getForCustomer: async (customerId: number, options?: { accountId?: number; folder?: 'INBOX' | 'SENT'; limit?: number; offset?: number }) => {
|
||||
const res = await api.get<ApiResponse<CachedEmail[]>>(`/customers/${customerId}/emails`, { params: options });
|
||||
return res.data;
|
||||
},
|
||||
// E-Mails für Vertrag abrufen
|
||||
getForContract: async (contractId: number, options?: { folder?: 'INBOX' | 'SENT'; limit?: number; offset?: number }) => {
|
||||
const res = await api.get<ApiResponse<CachedEmail[]>>(`/contracts/${contractId}/emails`, { params: options });
|
||||
return res.data;
|
||||
},
|
||||
// Ordner-Anzahlen für Vertrag abrufen (zugeordnete E-Mails)
|
||||
getContractFolderCounts: async (contractId: number) => {
|
||||
const res = await api.get<ApiResponse<{
|
||||
inbox: number;
|
||||
inboxUnread: number;
|
||||
sent: number;
|
||||
sentUnread: number;
|
||||
}>>(`/contracts/${contractId}/emails/folder-counts`);
|
||||
return res.data;
|
||||
},
|
||||
// Mailbox-Konten eines Kunden abrufen
|
||||
getMailboxAccounts: async (customerId: number) => {
|
||||
const res = await api.get<ApiResponse<MailboxAccount[]>>(`/customers/${customerId}/mailbox-accounts`);
|
||||
return res.data;
|
||||
},
|
||||
// Einzelne E-Mail abrufen (mit Body)
|
||||
getById: async (id: number) => {
|
||||
const res = await api.get<ApiResponse<CachedEmail>>(`/emails/${id}`);
|
||||
return res.data;
|
||||
},
|
||||
// E-Mail-Thread abrufen
|
||||
getThread: async (id: number) => {
|
||||
const res = await api.get<ApiResponse<CachedEmail[]>>(`/emails/${id}/thread`);
|
||||
return res.data;
|
||||
},
|
||||
// Als gelesen/ungelesen markieren
|
||||
markAsRead: async (id: number, isRead: boolean) => {
|
||||
const res = await api.patch<ApiResponse<void>>(`/emails/${id}/read`, { isRead });
|
||||
return res.data;
|
||||
},
|
||||
// Stern umschalten
|
||||
toggleStar: async (id: number) => {
|
||||
const res = await api.post<ApiResponse<{ isStarred: boolean }>>(`/emails/${id}/star`);
|
||||
return res.data;
|
||||
},
|
||||
// Vertrag zuordnen
|
||||
assignToContract: async (emailId: number, contractId: number) => {
|
||||
const res = await api.post<ApiResponse<CachedEmail>>(`/emails/${emailId}/assign`, { contractId });
|
||||
return res.data;
|
||||
},
|
||||
// Zuordnung aufheben
|
||||
unassignFromContract: async (emailId: number) => {
|
||||
const res = await api.delete<ApiResponse<CachedEmail>>(`/emails/${emailId}/assign`);
|
||||
return res.data;
|
||||
},
|
||||
// E-Mail löschen (nur Admin)
|
||||
delete: async (emailId: number) => {
|
||||
const res = await api.delete<ApiResponse<void>>(`/emails/${emailId}`);
|
||||
return res.data;
|
||||
},
|
||||
// Anhang-URL (view=true für inline anzeigen, sonst download)
|
||||
getAttachmentUrl: (emailId: number, filename: string, view?: boolean) => {
|
||||
const token = localStorage.getItem('token');
|
||||
const encodedFilename = encodeURIComponent(filename);
|
||||
const viewParam = view ? '&view=true' : '';
|
||||
return `${api.defaults.baseURL}/emails/${emailId}/attachments/${encodedFilename}?token=${token}${viewParam}`;
|
||||
},
|
||||
// Ungelesene E-Mails zählen
|
||||
getUnreadCount: async (params: { customerId?: number; contractId?: number }) => {
|
||||
const res = await api.get<ApiResponse<{ count: number }>>('/emails/unread-count', { params });
|
||||
return res.data;
|
||||
},
|
||||
// ==================== PAPIERKORB ====================
|
||||
// Papierkorb-E-Mails für Kunden abrufen
|
||||
getTrash: async (customerId: number) => {
|
||||
const res = await api.get<ApiResponse<CachedEmail[]>>(`/customers/${customerId}/emails/trash`);
|
||||
return res.data;
|
||||
},
|
||||
// Papierkorb-Anzahl für Kunden
|
||||
getTrashCount: async (customerId: number) => {
|
||||
const res = await api.get<ApiResponse<{ count: number }>>(`/customers/${customerId}/emails/trash/count`);
|
||||
return res.data;
|
||||
},
|
||||
// E-Mail aus Papierkorb wiederherstellen
|
||||
restore: async (emailId: number) => {
|
||||
const res = await api.post<ApiResponse<void>>(`/emails/${emailId}/restore`);
|
||||
return res.data;
|
||||
},
|
||||
// E-Mail endgültig löschen (aus Papierkorb)
|
||||
permanentDelete: async (emailId: number) => {
|
||||
const res = await api.delete<ApiResponse<void>>(`/emails/${emailId}/permanent`);
|
||||
return res.data;
|
||||
},
|
||||
};
|
||||
|
||||
// Contracts
|
||||
@@ -378,6 +601,51 @@ export const appSettingsApi = {
|
||||
},
|
||||
};
|
||||
|
||||
// Backup & Restore
|
||||
export interface BackupInfo {
|
||||
name: string;
|
||||
timestamp: string;
|
||||
totalRecords: number;
|
||||
tables: { table: string; count: number }[];
|
||||
sizeBytes: number;
|
||||
hasUploads: boolean;
|
||||
uploadSizeBytes: number;
|
||||
}
|
||||
|
||||
export const backupApi = {
|
||||
list: async () => {
|
||||
const res = await api.get<ApiResponse<BackupInfo[]>>('/settings/backups');
|
||||
return res.data;
|
||||
},
|
||||
create: async () => {
|
||||
const res = await api.post<ApiResponse<{ backupName: string }>>('/settings/backup');
|
||||
return res.data;
|
||||
},
|
||||
restore: async (name: string) => {
|
||||
const res = await api.post<ApiResponse<{ restoredRecords: number; restoredFiles: number }>>(`/settings/backup/${name}/restore`);
|
||||
return res.data;
|
||||
},
|
||||
delete: async (name: string) => {
|
||||
const res = await api.delete<ApiResponse<void>>(`/settings/backup/${name}`);
|
||||
return res.data;
|
||||
},
|
||||
getDownloadUrl: (name: string) => {
|
||||
return `/api/settings/backup/${name}/download`;
|
||||
},
|
||||
upload: async (file: File) => {
|
||||
const formData = new FormData();
|
||||
formData.append('backup', file);
|
||||
const res = await api.post<ApiResponse<{ backupName: string }>>('/settings/backup/upload', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
return res.data;
|
||||
},
|
||||
factoryReset: async () => {
|
||||
const res = await api.post<ApiResponse<void>>('/settings/factory-reset');
|
||||
return res.data;
|
||||
},
|
||||
};
|
||||
|
||||
// Platforms
|
||||
export const platformApi = {
|
||||
getAll: async (includeInactive = false) => {
|
||||
@@ -645,7 +913,7 @@ export const userApi = {
|
||||
const res = await api.get<ApiResponse<User>>(`/users/${id}`);
|
||||
return res.data;
|
||||
},
|
||||
create: async (data: { email: string; password: string; firstName: string; lastName: string; roleIds: number[]; customerId?: number }) => {
|
||||
create: async (data: { email: string; password: string; firstName: string; lastName: string; roleIds: number[]; customerId?: number; hasDeveloperAccess?: boolean }) => {
|
||||
const res = await api.post<ApiResponse<User>>('/users', data);
|
||||
return res.data;
|
||||
},
|
||||
@@ -698,6 +966,15 @@ export interface EmailProviderConfig {
|
||||
passwordEncrypted?: string;
|
||||
domain: string;
|
||||
defaultForwardEmail?: string;
|
||||
// IMAP/SMTP-Server (für E-Mail-Client)
|
||||
imapServer?: string;
|
||||
imapPort?: number;
|
||||
smtpServer?: string;
|
||||
smtpPort?: number;
|
||||
// Verschlüsselungs-Einstellungen
|
||||
imapEncryption?: 'SSL' | 'STARTTLS' | 'NONE';
|
||||
smtpEncryption?: 'SSL' | 'STARTTLS' | 'NONE';
|
||||
allowSelfSignedCerts?: boolean; // Selbstsignierte Zertifikate erlauben
|
||||
isActive: boolean;
|
||||
isDefault: boolean;
|
||||
createdAt: string;
|
||||
|
||||
Reference in New Issue
Block a user