- PostgreSQL: initdb durch rsync-Ansatz ersetzt (Ubuntu/Debian kompatibel)
Data-Dir wird via rsync vom Default-Cluster nach /data/postgresql migriert
- PostgreSQL: de_DE.UTF-8 Locale-Generierung als ersten Task hinzugefügt
- Redis: redis-cli ping mit Passwort-Auth (no_log: true)
- Playbooks: vars_files: ../vault.yml in dbs01/aps01/web01 ergänzt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /settings/ai-config: gibt { configured: boolean } zurück
- POST /settings/ai-config: speichert Key Base64-kodiert in Redis (PLATFORM_ADMIN)
- HelpService: dynamische Key-Auflösung aus Redis mit 60s In-Memory-Cache
- AdminAiSettingsPage: neue Admin-Seite /admin/ai-settings
- HelpPanel: zeigt Hinweis + Link wenn KI nicht konfiguriert
- Env-Variable ANTHROPIC_API_KEY hat weiterhin Vorrang (backward compat.)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ergänzt fehlende Migration für Stammdaten-Tabellen (departments,
locations, cost_centers, job_titles, skill_categories) — Tabellen
wurden bereits via SQL-Push auf dem Server erstellt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prisma Enums existieren als Runtime-Objekt nur nach prisma generate,
aber TypeScript im watch-mode erwartet importierbare Werte. Verwende
stattdessen direkte String-Vergleiche ('ALL', 'TEAM', 'OWN').
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prisma-Enums sind zur Laufzeit nicht als Objekt verfuegbar wenn ts-node
den Import vor der Client-Generierung auflöst. Ersetzt durch eigenen
Type-Export aus build-visibility-filter.ts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wenn ein Projekteintrag auf eine neue Seite überläuft (Tasks zu lang),
wird jetzt trotzdem eine pendingLine vom Seitenanfang der neuen Seite
bis zum Gap vor dem nächsten Eintrag vorgemerkt. Dadurch entsteht eine
durchgehende Timeline-Linie auf der Fortsetzungsseite, die den
überlaufenden Inhalt visuell mit dem nächsten Eintrag verbindet.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Vorherige Ansätze berechneten die Ziel-Header-Höhe am Ende des Eintrags
neu (fehleranfällig durch doppelte Font-State-Operationen). Neuer Ansatz:
Linie für Entry i wird am ANFANG von Entry i+1 gezeichnet, BEVOR der
Seitenumbruch-Check läuft — mit demselben headerH der bereits berechnet
wurde. Eine einzige Bedingung entscheidet konsistent ob Linie gezeichnet
wird UND ob ein Seitenumbruch folgt, ohne Redundanz oder State-Probleme.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Berechnet die tatsächliche Mindesthöhe des nächsten Projekteintrags
(Datum + Rolle + Firma) identisch zur Seitenumbruch-Logik. Verhindert
hängende Linien wenn der nächste Header > 40px hoch ist und eine neue
Seite benötigt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wenn ein Projekteintrag nahe am Seitenende endet und der nächste
Eintrag auf einer neuen Seite beginnt, wird keine Verbindungslinie
mehr gezeichnet. Vorher entstand ein hängender Strich ohne Ziel-Punkt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Buttons in sectionHeader (flex-direction: column) streckten sich auf
volle Containerbreite. align-self: flex-start begrenzt die Breite auf
min-width + Textinhalt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- darkenColor() Funktion: extrahierte Logo-Farbe um 30% abdunkeln
(gilt für PDF und DOCX Export) → kräftigerer, druckfreundlicher Ton
- ExpertProfileTab: min-width: 130px für btnPrimary und btnSecondary
→ alle Aktions-Buttons haben einheitliche Mindestbreite
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PDF-Export: doc.flushPages() vor doc.end() verhindert leere
Schlussseite (PDFKit bufferPages-Bug nach Footer-Loop)
- ExpertProfileTab: height: 32px für btnPrimary/Secondary/Danger
sowie chipInput- und headerForm-Inputs → einheitliche Höhe
- Primärfarbe #1a56db → #1040bb (dunkler, besser zum Logo passend)
- LoginPage CSS-Fallback-Gradient ebenfalls auf neue Primärfarbe
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root causes identified via DB hex-dump and server logs:
1. Tab character in budget lines: DB stores e.g. `**Budget Verantwortung:**\t750.000 EUR`
(byte 0x09 between `:**` and the amount). PDFKit can't render \t in WinAnsiEncoding,
producing garbage output like `"sSãUU`. Fix: `.replace(/\t/g, ' ')` in cleaned text.
2. Unconditional bullet: `\u2022 ${sanitize(hasBullet ? cleaned : cleaned)}` always
prepended `•` — the ternary was a no-op. Fix: only add `•` when hasBullet is true;
`**...**` header lines now render as Helvetica-Bold without a bullet.
3. ITIL4 Foundation Cert.pdf is owner-password-encrypted. pdf-lib threw
"Input document is encrypted" → cert was silently skipped.
Fix: `PdfLib.load(attBuffer, { ignoreEncryption: true })`.
Applies to both PDF and DOCX export paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LoginPage calls /settings/branding to load branding config (logo, colors).
Without @Public(), the JWT guard returns 401, which triggered the axios
response interceptor to attempt a silent refresh, fail, and call
window.location.href = '/login' — creating an infinite reload loop on
the login page itself.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add @SkipThrottle() to POST /auth/refresh so repeated silent-refresh calls
from page reloads no longer exhaust the rate limit (HTTP 429)
- Configure Vite HMR explicitly with host/clientPort/protocol=wss so the
WebSocket connects correctly through Traefik instead of reconnecting every ~1s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pdf-lib installiert und importiert
- PDF-Anhänge werden nicht mehr als Platzhalter-Seite angezeigt, sondern
alle Seiten des Anhang-PDFs direkt in das Export-PDF eingebettet (pdf-lib merge)
- Bild-Anhänge bleiben weiterhin per PDFKit eingebettet
- sanitizePdfText() erweitert: vollständige Windows-1252 U+0080-U+009F Mapping-Tabelle
für mis-enkodierte Zeichen (€, Anführungszeichen, Gedankenstriche, TM usw.)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pageBottom von 800 auf 740 reduziert: verhindert PDFKit-Auto-Seitenumbrüche
mitten in Projekt-Einträgen (bisher: Datum/Rolle/Firma je auf eigener Seite)
- Vor jedem Projekt-Eintrag Header-Höhe (Datum+Rolle+Firma) vorberechnen und
Seitenumbruch proaktiv auslösen, bevor der Header gezeichnet wird
- sanitizePdfText() Hilfsmethode: ersetzt €→EUR sowie Zeichen außerhalb Latin-1
die Helvetica (WinAnsiEncoding) nicht rendern kann (bisher: Zeichensalat)
- sanitizePdfText auf Projekt-Texte und Zertifizierungs-Texte angewendet
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PDF-Export: alle Anhänge als zusätzliche Seiten (Bilder als Vorschau, andere mit Hinweis)
- DOCX-Export: Bild-Anhänge als zusätzliche Sections (je eine Seite pro Bild)
- ExpertProfileTab: Skills/Sprachen/Erfahrungen nebeneinander (3 Spalten)
- ExpertProfileTab: Zertifizierungen und Profilanlagen nebeneinander (2 Spalten)
- threeColumnRow CSS-Klasse hinzugefügt (responsive auf 1 Spalte bei <900px)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Neue Prisma-Modelle ProfileAccessGroup + ProfileAccessGroupMember mit canView/canExport/canEdit
- Manuelle Migration 20260314_profile_access_groups
- ProfileAccessModule: CRUD-Endpoints für Gruppen und Mitglieder (nur PLATFORM_ADMIN)
- Neue Admin-Endpoints in ExpertProfileService/-Controller für alle Profil-Mutationen
- verifyOwnership mit skipCheck-Parameter für Admin-Bypass
- ExpertProfileTab + alle Section-Komponenten erhalten apiBase-Prop für Wiederverwendung
- AdminProfileAccessPage: Gruppen-Tab (CRUD) + Profile-Tab (alle User mit Aktionen)
- AdminProfileDetailPage: Profil eines beliebigen Users im Admin-Kontext bearbeiten
- Route /admin/profile-access + /admin/profiles/:userId + Nav-Tab Profilzugriff
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Jobtitel aus Profil statt experiences[0].area unter dem Namen
- Platform-Logo aus Redis über Avatar einfügen
- Dominante Akzentfarbe dynamisch aus Logo extrahieren
- Firmen-Fußzeile aus Redis-Settings als DOCX-Footer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- getExportData(): jobTitle zu den selektierten User-Feldern hinzugefuegt
- ExportData-Interface: jobTitle: string | null ergaenzt
- PDF: Unter dem Namen wird jetzt data.jobTitle angezeigt statt
profile.experiences[0].area
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- extractDominantColor(): 20x20 Resize via sharp, Alpha gegen Weiss flatten,
alle gesaettigten Pixel (nicht weiss/schwarz/grau, range > 35) mitteln
- Ergebnis wird als accentColor fuer Timeline-Linien, Ueberschriften,
Skill-Chips usw. verwendet
- Fallback auf #009688 wenn kein Logo hinterlegt oder keine Farbe extrahierbar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Footer: doc.page.margins.bottom temporaer auf 0 gesetzt beim Footer-Zeichnen
verhindert PDFKit Auto-Pagination (footerTextY > maxY wuerde sonst
eine leere zweite Seite erzeugen)
- Logo: Platform-Branding-Logo (aus Redis platform_branding_logo) wird oben
in der linken Spalte ueber dem Profilfoto gerendert (fit 180x50px)
- Firmendaten + Branding parallel via Promise.all aus Redis geladen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ZERTIFIZIERUNGEN von rechter Spalte in linke Spalte verschoben
(nach ERFAHRUNG, vor FÄHIGKEITEN)
- Textbreite auf leftColWidth (certContentWidth = leftColWidth - 14) angepasst
- Timeline-Linie dynamisch (certEntryStartY gespeichert, Linie nach Content)
- Alte rechte-Spalte-Sektion entfernt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pdfSectionTitle: Linie zieht sich jetzt ueber die volle Spaltenbreite (width-Parameter)
statt nur bis zum Textende
- Titel 'BERUFSERFAHRUNG' umbenannt in 'BERUFSERFAHRUNG / PROJEKTE'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- BERUFSERFAHRUNG: Timeline-Linie wird jetzt NACH dem Content gezeichnet
(entryStartY gespeichert, Linie von entryStartY+8 bis yRight-4)
Damit stimmt die Laenge exakt mit der tatsaechlichen Eintraghoehe ueberein
- Seitenumbruch-Flag (pageBreakOccurred): Linie wird nicht gezeichnet wenn
der Content ueber eine Seite hinausgeht
- FAEHIGKEITEN: aus dem full-width Bereich am Seitenende entfernt und in die
linke Spalte nach ERFAHRUNG verschoben (kleinere Chips: 7pt, 16px, 5px Pad)
- Alte full-width FAEHIGKEITEN-Sektion entfernt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Tasks: Bullet-Praefix nur fuer Zeilen mit echtem Aufzaehlungszeichen (kein spurious Bullet bei Plaintext)
- Zertifizierungen: Schriftgroesse reduziert (Titel 10->9pt, Aussteller 9->8pt) und Timeline-Linie gekuerzt
- Anhaenge: Bild-Anhaenge werden als zusaetzliche Seiten ans PDF angehaengt
- ExportData-Interface + getExportData() um attachments[] erweitert
- Gleiche Bullet-Fix-Logik im DOCX-Export
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 Korrekturen am Experten-Profil PDF-Export:
1. Dateiname: Vorname_Nachname_CV.pdf/.docx (RFC 5987 Umlaut-sicher)
2. Fettschrift: Zeitraum, Firmenname und Branche in Berufserfahrung
3. Zeichen-Darstellung: Markdown-Marker (**bold**/*italic*/__u__)
werden aus Tasks-Text entfernt, doppelte Bullet-Praefix-Normalisierung
4. Abstaende: Sprachen 14->11px, Erfahrung 14->11px, Gap 8->4px
5. Zertifizierungen in rechte Spalte verschoben (nach Berufserfahrung)
statt als Vollbreite-Sektion am Ende
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Im Bereich Anpassungen (AdminCustomizePage) kann der Platform-Admin
nun den Login-Screen individuell gestalten:
- Hintergrundtyp: Farbverlauf, Einfarbig oder Hintergrundbild
- Farbverlauf: zwei Farbpicker (Von/Bis) mit Hex-Eingabe
- Hintergrundbild: Datei-Upload max. 2MB, Live-Vorschau
- Logo auf Login-Screen: wird automatisch aus dem Sidebar-Logo uebernommen
Backend: settings.controller.ts GET/POST /settings/branding um
loginBgType, loginBgColor1, loginBgColor2, loginBgImage erweitert.
LoginPage laedt Branding per oeffentlichem Endpoint (kein Auth), leitet
containerStyle per useMemo ab und zeigt Logo-Bild statt Hardcode-Text.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>