427 Zeilen
14 KiB
Python
427 Zeilen
14 KiB
Python
"""
|
|
Profil-Export-Service für Account-Daten
|
|
|
|
Exportiert Account-Profile in verschiedene Formate (CSV, JSON, PDF).
|
|
"""
|
|
|
|
import os
|
|
import csv
|
|
import json
|
|
import string
|
|
import logging
|
|
from io import BytesIO, StringIO
|
|
from datetime import datetime
|
|
from typing import Dict, Any, List
|
|
from pathlib import Path
|
|
|
|
logger = logging.getLogger("profile_export_service")
|
|
|
|
|
|
class ProfileExportService:
|
|
"""Service für den Export von Account-Profilen"""
|
|
|
|
# Felder die exportiert werden (ohne id, last_login, notes, status)
|
|
EXPORT_FIELDS = {
|
|
"username": "Username",
|
|
"password": "Passwort",
|
|
"email": "E-Mail",
|
|
"phone": "Telefon",
|
|
"platform": "Plattform",
|
|
"full_name": "Name",
|
|
"created_at": "Erstellt_am"
|
|
}
|
|
|
|
@staticmethod
|
|
def export_to_csv(account_data: Dict[str, Any]) -> bytes:
|
|
"""
|
|
Exportiert Account-Daten als CSV.
|
|
|
|
Args:
|
|
account_data: Dictionary mit Account-Daten
|
|
|
|
Returns:
|
|
CSV-Daten als bytes
|
|
"""
|
|
try:
|
|
output = StringIO()
|
|
writer = csv.DictWriter(
|
|
output,
|
|
fieldnames=ProfileExportService.EXPORT_FIELDS.values(),
|
|
quoting=csv.QUOTE_MINIMAL
|
|
)
|
|
|
|
# Header schreiben
|
|
writer.writeheader()
|
|
|
|
# Datenzeile vorbereiten
|
|
row_data = {}
|
|
for field_key, field_label in ProfileExportService.EXPORT_FIELDS.items():
|
|
value = account_data.get(field_key, "")
|
|
# None zu leerem String konvertieren
|
|
row_data[field_label] = value if value is not None else ""
|
|
|
|
# Datenzeile schreiben
|
|
writer.writerow(row_data)
|
|
|
|
# In bytes konvertieren (UTF-8)
|
|
csv_content = output.getvalue()
|
|
output.close()
|
|
|
|
logger.info("CSV-Export erfolgreich")
|
|
return csv_content.encode('utf-8')
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim CSV-Export: {e}")
|
|
raise
|
|
|
|
@staticmethod
|
|
def export_to_txt(account_data: Dict[str, Any]) -> bytes:
|
|
"""
|
|
Exportiert Account-Daten als TXT (Key-Value Format).
|
|
|
|
Args:
|
|
account_data: Dictionary mit Account-Daten
|
|
|
|
Returns:
|
|
TXT-Daten als bytes
|
|
"""
|
|
try:
|
|
lines = []
|
|
|
|
for field_key, field_label in ProfileExportService.EXPORT_FIELDS.items():
|
|
value = account_data.get(field_key, "")
|
|
# None zu leerem String konvertieren
|
|
value = value if value is not None else ""
|
|
lines.append(f"{field_label}: {value}")
|
|
|
|
# Exportdatum hinzufügen
|
|
export_date = datetime.now().strftime("%d.%m.%Y %H:%M")
|
|
lines.append(f"\nExportiert am: {export_date}")
|
|
|
|
txt_content = "\n".join(lines)
|
|
|
|
logger.info("TXT-Export erfolgreich")
|
|
return txt_content.encode('utf-8')
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim TXT-Export: {e}")
|
|
raise
|
|
|
|
@staticmethod
|
|
def export_to_json(account_data: Dict[str, Any]) -> bytes:
|
|
"""
|
|
Exportiert Account-Daten als JSON.
|
|
|
|
Args:
|
|
account_data: Dictionary mit Account-Daten
|
|
|
|
Returns:
|
|
JSON-Daten als bytes
|
|
"""
|
|
try:
|
|
# JSON-Struktur aufbauen
|
|
export_data = {
|
|
"account": {
|
|
field_key: account_data.get(field_key, "")
|
|
for field_key in ProfileExportService.EXPORT_FIELDS.keys()
|
|
},
|
|
"export_info": {
|
|
"exported_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
"format_version": "1.0"
|
|
}
|
|
}
|
|
|
|
# None-Werte zu leeren Strings konvertieren
|
|
for key, value in export_data["account"].items():
|
|
if value is None:
|
|
export_data["account"][key] = ""
|
|
|
|
# JSON mit Einrückung für Lesbarkeit
|
|
json_content = json.dumps(
|
|
export_data,
|
|
ensure_ascii=False,
|
|
indent=2,
|
|
sort_keys=False
|
|
)
|
|
|
|
logger.info("JSON-Export erfolgreich")
|
|
return json_content.encode('utf-8')
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim JSON-Export: {e}")
|
|
raise
|
|
|
|
@staticmethod
|
|
def export_to_pdf(account_data: Dict[str, Any]) -> bytes:
|
|
"""
|
|
Exportiert Account-Daten als PDF mit schönem Layout.
|
|
|
|
Args:
|
|
account_data: Dictionary mit Account-Daten
|
|
|
|
Returns:
|
|
PDF-Daten als bytes
|
|
"""
|
|
try:
|
|
from reportlab.lib.pagesizes import A4
|
|
from reportlab.lib.units import mm
|
|
from reportlab.lib import colors
|
|
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image
|
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
|
from reportlab.lib.enums import TA_LEFT, TA_CENTER
|
|
|
|
# PDF-Buffer erstellen
|
|
buffer = BytesIO()
|
|
|
|
# Dokument erstellen
|
|
doc = SimpleDocTemplate(
|
|
buffer,
|
|
pagesize=A4,
|
|
rightMargin=20*mm,
|
|
leftMargin=20*mm,
|
|
topMargin=20*mm,
|
|
bottomMargin=20*mm
|
|
)
|
|
|
|
# Story (Inhalt) erstellen
|
|
story = []
|
|
styles = getSampleStyleSheet()
|
|
|
|
# Custom Styles
|
|
title_style = ParagraphStyle(
|
|
'CustomTitle',
|
|
parent=styles['Title'],
|
|
fontSize=20,
|
|
textColor=colors.HexColor('#1F2937'),
|
|
spaceAfter=5*mm,
|
|
alignment=TA_CENTER
|
|
)
|
|
|
|
heading_style = ParagraphStyle(
|
|
'CustomHeading',
|
|
parent=styles['Heading2'],
|
|
fontSize=14,
|
|
textColor=colors.HexColor('#374151'),
|
|
spaceAfter=3*mm,
|
|
spaceBefore=5*mm
|
|
)
|
|
|
|
# IntelSight Logo versuchen zu laden
|
|
logo_path = Path("resources/icons/intelsight-logo.svg")
|
|
if logo_path.exists():
|
|
try:
|
|
# SVG zu reportlab Image (mit svglib falls verfügbar)
|
|
try:
|
|
from svglib.svglib import svg2rlg
|
|
from reportlab.graphics import renderPDF
|
|
|
|
drawing = svg2rlg(str(logo_path))
|
|
if drawing:
|
|
# Skalieren auf vernünftige Größe
|
|
drawing.width = 40*mm
|
|
drawing.height = 10*mm
|
|
drawing.scale(40*mm/drawing.width, 10*mm/drawing.height)
|
|
story.append(drawing)
|
|
story.append(Spacer(1, 5*mm))
|
|
except ImportError:
|
|
# svglib nicht verfügbar - überspringen
|
|
logger.debug("svglib nicht verfügbar - Logo wird übersprungen")
|
|
except Exception as e:
|
|
logger.debug(f"Logo konnte nicht geladen werden: {e}")
|
|
|
|
# Titel
|
|
username = account_data.get("username", "Unbekannt")
|
|
platform = account_data.get("platform", "Unbekannt")
|
|
title = Paragraph(f"Account-Profil: {username}", title_style)
|
|
story.append(title)
|
|
|
|
# Plattform
|
|
platform_text = Paragraph(f"Plattform: {platform.title()}", styles['Normal'])
|
|
story.append(platform_text)
|
|
story.append(Spacer(1, 10*mm))
|
|
|
|
# LOGIN-DATEN Sektion
|
|
login_heading = Paragraph("LOGIN-DATEN", heading_style)
|
|
story.append(login_heading)
|
|
|
|
# Login-Daten Tabelle
|
|
login_data = [
|
|
["Benutzername:", account_data.get("username", "")],
|
|
["Passwort:", account_data.get("password", "")],
|
|
["E-Mail:", account_data.get("email", "") or "-"],
|
|
["Telefon:", account_data.get("phone", "") or "-"]
|
|
]
|
|
|
|
login_table = Table(login_data, colWidths=[45*mm, 115*mm])
|
|
login_table.setStyle(TableStyle([
|
|
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
|
|
('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
|
|
('FONTSIZE', (0, 0), (-1, -1), 11),
|
|
('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#6B7280')),
|
|
('TEXTCOLOR', (1, 0), (1, -1), colors.HexColor('#1F2937')),
|
|
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
|
('LEFTPADDING', (0, 0), (-1, -1), 0),
|
|
('RIGHTPADDING', (0, 0), (-1, -1), 0),
|
|
('TOPPADDING', (0, 0), (-1, -1), 2*mm),
|
|
('BOTTOMPADDING', (0, 0), (-1, -1), 2*mm),
|
|
]))
|
|
|
|
story.append(login_table)
|
|
story.append(Spacer(1, 8*mm))
|
|
|
|
# PROFIL-INFORMATIONEN Sektion
|
|
profile_heading = Paragraph("PROFIL-INFORMATIONEN", heading_style)
|
|
story.append(profile_heading)
|
|
|
|
# Profil-Daten Tabelle
|
|
profile_data = [
|
|
["Name:", account_data.get("full_name", "") or "-"],
|
|
["Erstellt am:", account_data.get("created_at", "") or "-"]
|
|
]
|
|
|
|
profile_table = Table(profile_data, colWidths=[45*mm, 115*mm])
|
|
profile_table.setStyle(TableStyle([
|
|
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
|
|
('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
|
|
('FONTSIZE', (0, 0), (-1, -1), 11),
|
|
('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#6B7280')),
|
|
('TEXTCOLOR', (1, 0), (1, -1), colors.HexColor('#1F2937')),
|
|
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
|
('LEFTPADDING', (0, 0), (-1, -1), 0),
|
|
('RIGHTPADDING', (0, 0), (-1, -1), 0),
|
|
('TOPPADDING', (0, 0), (-1, -1), 2*mm),
|
|
('BOTTOMPADDING', (0, 0), (-1, -1), 2*mm),
|
|
]))
|
|
|
|
story.append(profile_table)
|
|
story.append(Spacer(1, 15*mm))
|
|
|
|
# Export-Datum (Footer)
|
|
export_date = datetime.now().strftime("%d.%m.%Y %H:%M")
|
|
footer_text = Paragraph(
|
|
f"Exportiert am: {export_date}",
|
|
ParagraphStyle(
|
|
'Footer',
|
|
parent=styles['Normal'],
|
|
fontSize=9,
|
|
textColor=colors.HexColor('#9CA3AF'),
|
|
alignment=TA_CENTER
|
|
)
|
|
)
|
|
story.append(footer_text)
|
|
|
|
# PDF erstellen
|
|
doc.build(story)
|
|
|
|
# Buffer-Wert holen
|
|
pdf_content = buffer.getvalue()
|
|
buffer.close()
|
|
|
|
logger.info("PDF-Export erfolgreich")
|
|
return pdf_content
|
|
|
|
except ImportError as e:
|
|
logger.error(f"reportlab nicht installiert: {e}")
|
|
raise Exception(
|
|
"PDF-Export erfordert 'reportlab' Library. "
|
|
"Bitte installieren Sie: pip install reportlab"
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim PDF-Export: {e}")
|
|
raise
|
|
|
|
@staticmethod
|
|
def generate_filename(
|
|
account_data: Dict[str, Any],
|
|
format_extension: str,
|
|
include_timestamp: bool = True
|
|
) -> str:
|
|
"""
|
|
Generiert einen Dateinamen nach Konvention.
|
|
|
|
Format: {username}_{platform}_{timestamp}.{extension}
|
|
Beispiel: max_mueller_instagram_2025-11-10_14-30.csv
|
|
|
|
Args:
|
|
account_data: Account-Daten
|
|
format_extension: Dateiendung (z.B. "csv", "txt", "pdf", "zip")
|
|
include_timestamp: Ob Timestamp hinzugefügt werden soll
|
|
|
|
Returns:
|
|
Generierter Dateiname
|
|
"""
|
|
# Username und Platform aus Account-Daten
|
|
username = account_data.get("username", "account")
|
|
platform = account_data.get("platform", "unknown").lower()
|
|
|
|
# Sonderzeichen entfernen für Dateinamen
|
|
username = ProfileExportService._sanitize_filename(username)
|
|
platform = ProfileExportService._sanitize_filename(platform)
|
|
|
|
# Timestamp
|
|
if include_timestamp:
|
|
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M")
|
|
filename = f"{username}_{platform}_{timestamp}.{format_extension}"
|
|
else:
|
|
filename = f"{username}_{platform}.{format_extension}"
|
|
|
|
return filename
|
|
|
|
@staticmethod
|
|
def _sanitize_filename(filename: str) -> str:
|
|
"""
|
|
Entfernt ungültige Zeichen aus Dateinamen.
|
|
|
|
Args:
|
|
filename: Ursprünglicher Dateiname
|
|
|
|
Returns:
|
|
Bereinigter Dateiname
|
|
"""
|
|
# Nur alphanumerische Zeichen, Unterstrich und Bindestrich erlauben
|
|
valid_chars = f"-_.{string.ascii_letters}{string.digits}"
|
|
sanitized = ''.join(c if c in valid_chars else '_' for c in filename)
|
|
return sanitized
|
|
|
|
@staticmethod
|
|
def export_account(
|
|
account_data: Dict[str, Any],
|
|
formats: List[str],
|
|
password_protect: bool = False
|
|
) -> Dict[str, bytes]:
|
|
"""
|
|
Exportiert Account-Daten in angegebene Formate.
|
|
|
|
Args:
|
|
account_data: Account-Daten zum Exportieren
|
|
formats: Liste von Formaten ["csv", "json", "pdf"]
|
|
password_protect: Wird ignoriert (für Rückwärtskompatibilität)
|
|
|
|
Returns:
|
|
Dict mit {filename: content} für alle Formate
|
|
"""
|
|
files_dict = {}
|
|
|
|
# Jedes Format exportieren
|
|
for fmt in formats:
|
|
fmt = fmt.lower()
|
|
|
|
if fmt == "csv":
|
|
content = ProfileExportService.export_to_csv(account_data)
|
|
filename = ProfileExportService.generate_filename(account_data, "csv")
|
|
files_dict[filename] = content
|
|
|
|
elif fmt == "json":
|
|
content = ProfileExportService.export_to_json(account_data)
|
|
filename = ProfileExportService.generate_filename(account_data, "json")
|
|
files_dict[filename] = content
|
|
|
|
elif fmt == "pdf":
|
|
content = ProfileExportService.export_to_pdf(account_data)
|
|
filename = ProfileExportService.generate_filename(account_data, "pdf")
|
|
files_dict[filename] = content
|
|
else:
|
|
logger.warning(f"Unbekanntes Format ignoriert: {fmt}")
|
|
|
|
return files_dict
|