# Changelog - BizMatch Project Dokumentation aller wichtigen Änderungen am BizMatch-Projekt. Diese Datei listet Feature-Implementierungen, Bugfixes, Datenbank-Migrationen und architektonische Verbesserungen auf. --- ## Inhaltsverzeichnis 1. [Datenbank-Änderungen](#1-datenbank-änderungen) 2. [Backend-Änderungen](#2-backend-änderungen) 3. [Frontend-Änderungen](#3-frontend-änderungen) 4. [SEO-Verbesserungen](#4-seo-verbesserungen) 5. [Code-Cleanup & Wartung](#5-code-cleanup--wartung) 6. [Bekannte Issues & Workarounds](#6-bekannte-issues--workarounds) --- ## 1) Datenbank-Änderungen ### 1.1 Schema-Migration: JSON-basierte Speicherung **Datum:** November 2025 **Status:** ✅ Abgeschlossen #### Zusammenfassung der Änderungen Die Datenbank wurde von einem **relationalen Schema** zu einem **JSON-basierten Schema** migriert. Dies bedeutet: - ✅ **Neue Tabellen wurden erstellt** (`users_json`, `businesses_json`, `commercials_json`) - ❌ **Alte Tabellen wurden NICHT gelöscht** (`users`, `businesses`, `commercials` existieren noch) - ✅ **Alle Daten wurden migriert** (kopiert von alten zu neuen Tabellen) - ✅ **Anwendung nutzt ausschließlich neue Tabellen** (alte Tabellen dienen nur als Backup) #### Detaillierte Tabellenstruktur **ALTE Tabellen (nicht mehr in Verwendung, aber noch vorhanden):** ```sql -- users (relational) CREATE TABLE users ( id UUID PRIMARY KEY, email VARCHAR(255), firstname VARCHAR(100), lastname VARCHAR(100), phone VARCHAR(50), location_name VARCHAR(255), location_state VARCHAR(2), location_latitude FLOAT, location_longitude FLOAT, customer_type VARCHAR(50), customer_sub_type VARCHAR(50), show_in_directory BOOLEAN, created TIMESTAMP, updated TIMESTAMP, -- ... weitere 20+ Spalten ); -- businesses (relational) CREATE TABLE businesses ( id UUID PRIMARY KEY, email VARCHAR(255), title VARCHAR(500), asking_price DECIMAL, established INTEGER, revenue DECIMAL, cash_flow DECIMAL, -- ... weitere 30+ Spalten für alle Business-Eigenschaften ); -- commercials (relational) CREATE TABLE commercials ( id UUID PRIMARY KEY, email VARCHAR(255), title VARCHAR(500), asking_price DECIMAL, property_type VARCHAR(100), -- ... weitere 25+ Spalten für alle Property-Eigenschaften ); ``` **NEUE Tabellen (aktuell in Verwendung):** ```sql -- users_json (JSON-basiert) CREATE TABLE users_json ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email VARCHAR(255) UNIQUE NOT NULL, data JSONB NOT NULL ); -- businesses_json (JSON-basiert) CREATE TABLE businesses_json ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email VARCHAR(255) NOT NULL, data JSONB NOT NULL ); -- commercials_json (JSON-basiert) CREATE TABLE commercials_json ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email VARCHAR(255) NOT NULL, data JSONB NOT NULL ); ``` #### Was wurde NICHT geändert **❌ Folgende Dinge wurden NICHT geändert:** 1. **Alte Tabellen existieren weiterhin** - Sie wurden nicht gelöscht, um als Backup zu dienen 2. **Datenbank-Name** - Weiterhin `bizmatch` (keine Änderung) 3. **Datenbank-User** - Weiterhin `bizmatch` (keine Änderung) 4. **Datenbank-Passwort** - Keine Änderung 5. **Datenbank-Port** - Weiterhin `5432` (keine Änderung) 6. **Docker-Container-Name** - Weiterhin `bizmatchdb` (keine Änderung) 7. **Indices** - PostgreSQL JSONB-Indices wurden automatisch erstellt #### Was wurde geändert **✅ Folgende Dinge wurden geändert:** 1. **Anwendungs-Code verwendet nur noch neue Tabellen:** - Backend liest/schreibt nur noch in `users_json`, `businesses_json`, `commercials_json` - Drizzle ORM Schema wurde aktualisiert (`bizmatch-server/src/drizzle/schema.ts`) - Alle Services wurden angepasst (user.service.ts, business-listing.service.ts, etc.) 2. **Datenstruktur in JSONB-Spalten:** - Alle Felder, die vorher einzelne Spalten waren, sind jetzt in der `data`-Spalte als JSON - Nested Objects möglich (z.B. `location` mit `name`, `state`, `latitude`, `longitude`) - Arrays direkt im JSON speicherbar (z.B. `imageOrder`, `areasServed`) 3. **Query-Syntax:** - Statt `WHERE firstname = 'John'` → `WHERE (data->>'firstname') = 'John'` - Statt `WHERE location_state = 'TX'` → `WHERE (data->'location'->>'state') = 'TX'` - Haversine-Formel für Radius-Suche nutzt jetzt JSON-Pfade #### Beispiel: User-Datensatz Vorher/Nachher **VORHER (relationale Tabelle `users`):** | id | email | firstname | lastname | phone | location_name | location_state | location_latitude | location_longitude | customer_type | customer_sub_type | show_in_directory | created | updated | |---|---|---|---|---|---|---|---|---|---|---|---|---|---| | abc-123 | john@example.com | John | Doe | +1-555-0123 | Austin | TX | 30.2672 | -97.7431 | professional | broker | true | 2025-11-01 | 2025-11-25 | **NACHHER (JSON-Tabelle `users_json`):** | id | email | data (JSONB) | |---|---|---| | abc-123 | john@example.com | `{"firstname": "John", "lastname": "Doe", "phone": "+1-555-0123", "location": {"name": "Austin", "state": "TX", "latitude": 30.2672, "longitude": -97.7431}, "customerType": "professional", "customerSubType": "broker", "showInDirectory": true, "created": "2025-11-01T10:00:00Z", "updated": "2025-11-25T15:30:00Z"}` | #### Vorteile der neuen Struktur - ✅ **Keine Schema-Migrationen mehr nötig** - Neue Felder einfach im JSON hinzufügen - ✅ **Flexiblere Datenstrukturen** - Nested Objects und Arrays direkt speicherbar - ✅ **Einfacheres ORM-Mapping** - TypeScript-Interfaces direkt zu JSON serialisierbar - ✅ **Bessere Performance** - PostgreSQL JSONB ist indexierbar und schnell durchsuchbar - ✅ **Reduzierte Code-Komplexität** - Weniger Join-Operationen, weniger Spalten-Mapping #### Migration durchführen (Referenz) Die Migration wurde bereits durchgeführt. Falls nötig, Backup-Prozess: ```bash # 1. Backup der alten relationalen Tabellen docker exec -it bizmatchdb \ pg_dump -U bizmatch -d bizmatch -t users -t businesses -t commercials \ -F c -Z 9 -f /tmp/backup_relational_tables.dump # 2. Neue Tabellen sind bereits vorhanden und in Verwendung # 3. Alte Tabellen können bei Bedarf gelöscht werden (NICHT empfohlen vor finalem Produktions-Test) ``` ### 1.2 Location-Datenstruktur bei Professionals **Datum:** November 2025 **Status:** ✅ Abgeschlossen #### Problem Professionals-Suche funktionierte nicht, da die Datenstruktur für `location` falsch angenommen wurde. #### Lösung - Professionals verwenden `location`-Objekt (nicht `areasServed`-Array) - Struktur: `{ name: 'Austin', state: 'TX', latitude: 30.2672, longitude: -97.7431 }` #### Betroffene Queries - Exact City Search: `location.name` ILIKE-Vergleich - Radius Search: Haversine-Formel mit `location.latitude` und `location.longitude` --- ## 2) Backend-Änderungen ### 2.1 Professionals Search Fix **Datei:** `bizmatch-server/src/user/user.service.ts` **Status:** ✅ Abgeschlossen #### Änderungen **Exact City Search (Zeile 28-30):** ```typescript if (criteria.city && criteria.searchType === 'exact') { whereConditions.push(sql`(${schema.users_json.data}->'location'->>'name') ILIKE ${criteria.city.name}`); } ``` **Radius Search (Zeile 32-36):** ```typescript if (criteria.city && criteria.radius && criteria.searchType === 'radius' && criteria.radius) { const cityGeo = this.geoService.getCityWithCoords(criteria.state, criteria.city.name); const distanceQuery = getDistanceQuery(schema.users_json, cityGeo.latitude, cityGeo.longitude); whereConditions.push(sql`${distanceQuery} <= ${criteria.radius}`); } ``` **State Filter (Zeile 55-57):** ```typescript if (criteria.state) { whereConditions.push(sql`(${schema.users_json.data}->'location'->>'state') = ${criteria.state}`); } ``` **County Filter (Zeile 51-53):** ```typescript if (criteria.counties && criteria.counties.length > 0) { whereConditions.push(or(...criteria.counties.map(county => sql`(${schema.users_json.data}->'location'->>'county') ILIKE ${`%${county}%`}` ))); } ``` ### 2.2 TypeScript-Fehler behoben **Problem:** Kompilierungsfehler wegen falscher Parameter-Übergabe an `getDistanceQuery()` **Lösung:** - ❌ Alt: `getDistanceQuery(schema.users_json.data, 'location', lat, lon)` - ✅ Neu: `getDistanceQuery(schema.users_json, lat, lon)` ### 2.3 Slug-Unterstützung für SEO-freundliche URLs **Status:** ✅ Implementiert Business- und Commercial-Property-Listings können nun über SEO-freundliche Slugs aufgerufen werden: - `/business/austin-coffee-shop-for-sale` statt `/business/uuid-123-456` - `/commercial-property/downtown-retail-space-dallas` statt `/commercial-property/uuid-789` **Implementierung:** - Automatische Slug-Generierung aus Listing-Titeln - Slug-basierte Routen in allen Controllern - Fallback auf ID, falls kein Slug vorhanden --- ## 3) Frontend-Änderungen ### 3.1 Breadcrumbs-Navigation **Datum:** November 2025 **Status:** ✅ Abgeschlossen #### Implementierte Komponenten **Betroffene Seiten:** - ✅ Business Detail Pages (`details-business-listing.component.ts`) - ✅ Commercial Property Detail Pages (`details-commercial-property-listing.component.ts`) - ✅ User Detail Pages (`details-user.component.ts`) - ✅ 404 Not Found Page (`not-found.component.ts`) - ✅ Business Listings Overview (`business-listings.component.ts`) - ✅ Commercial Property Listings Overview (`commercial-property-listings.component.ts`) **Beispiel-Struktur:** ``` Home > Commercial Properties > Austin Office Space for Sale Home > Business Listings > Restaurant for Sale in Dallas Home > 404 - Page Not Found ``` **Komponente:** `bizmatch/src/app/components/breadcrumbs/breadcrumbs.component.ts` ### 3.2 Automatische 50-Meilen-Radius-Auswahl **Datum:** November 2025 **Status:** ✅ Abgeschlossen #### Änderungen Bei Auswahl einer Stadt in den Suchfiltern wird automatisch: - **Search Type** auf `"radius"` gesetzt - **Radius** auf `50` Meilen gesetzt - Filter-UI aktualisiert (Radius-Feld aktiv und ausgefüllt) **Betroffene Dateien:** - `bizmatch/src/app/components/search-modal/search-modal.component.ts` (Business) - `bizmatch/src/app/components/search-modal/search-modal-commercial.component.ts` (Properties) - `bizmatch/src/app/components/search-modal/search-modal-broker.component.ts` (Professionals) **Implementierung (Zeilen 255-269 in search-modal.component.ts):** ```typescript setCity(city: any): void { const updates: any = {}; if (city) { updates.city = city; updates.state = city.state; // Automatically set radius to 50 miles and enable radius search updates.searchType = 'radius'; updates.radius = 50; } else { updates.city = null; updates.radius = null; updates.searchType = 'exact'; } this.updateCriteria(updates); } ``` ### 3.3 Error Handling Verbesserungen **Betroffene Komponenten:** - `details-business-listing.component.ts` - `details-commercial-property-listing.component.ts` **Änderungen:** - ✅ Safe Navigation für `listing.imageOrder` (Null-Check vor forEach) - ✅ Verbesserte Error-Message-Extraktion mit Optional Chaining - ✅ Default-Breadcrumbs auch bei Fehlerfall - ✅ Korrekte Navigation zu 404-Seite bei fehlenden Listings **Beispiel (Zeile 139 in details-commercial-property-listing.component.ts):** ```typescript if (this.listing.imageOrder && Array.isArray(this.listing.imageOrder)) { this.listing.imageOrder.forEach(image => { const imageURL = `${this.env.imageBaseUrl}/pictures/property/${this.listing.imagePath}/${this.listing.serialId}/${image}`; this.images.push(new ImageItem({ src: imageURL, thumb: imageURL })); }); } ``` ### 3.4 Business Location Privacy - Stadt-Grenze statt exakter Adresse **Datum:** November 2025 **Status:** ✅ Abgeschlossen #### Problem & Motivation Bei Business-Listings für verkaufende Unternehmen sollte die **exakte Adresse nicht öffentlich angezeigt** werden, um: - ✅ Konkurrierende Unternehmen nicht zu informieren - ✅ Kunden nicht zu verunsichern - ✅ Mitarbeiter vor Verunsicherung zu schützen Nur die **ungefähre Stadt-Region** soll angezeigt werden. Die genaue Adresse wird erst nach Kontaktaufnahme mitgeteilt. #### Implementierung **Betroffene Dateien:** - `bizmatch/src/app/services/geo.service.ts` - Neue Methode `getCityBoundary()` - `bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts` - Override von Map-Methoden **Unterschied: Business vs. Commercial Property** | Listing-Typ | Map-Anzeige | Adresse-Anzeige | Begründung | |---|---|---|---| | **Business Listings** | Stadt-Grenze (rotes Polygon) | Nur Stadt, County, State | Privacy: Verkäufer-Schutz | | **Commercial Properties** | Exakter Pin-Marker | Vollständige Straßenadresse | Immobilie muss sichtbar sein | **Technische Umsetzung:** 1. **Nominatim API Integration** ([geo.service.ts:33-37](bizmatch/src/app/services/geo.service.ts#L33-L37)): ```typescript getCityBoundary(cityName: string, state: string): Observable { const query = `${cityName}, ${state}, USA`; let headers = new HttpHeaders().set('X-Hide-Loading', 'true').set('Accept-Language', 'en-US'); return this.http.get(`${this.baseUrl}?q=${encodeURIComponent(query)}&format=json&polygon_geojson=1&limit=1`, { headers }); } ``` 2. **City Boundary Polygon** ([details-business-listing.component.ts:322-430](bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts#L322-L430)): - Lädt Stadt-Grenz-Polygon von OpenStreetMap Nominatim API - Zeigt rote Umrandung der Stadt (wie Google Maps) - Unterstützt `Polygon` und `MultiPolygon` Geometrien - **Fallback:** 8km-Radius-Kreis bei API-Fehler oder fehlenden Daten 3. **Karten-Konfiguration:** ```typescript // Rotes Polygon (wie Google Maps) const cityPolygon = polygon(latlngs, { color: '#ef4444', // Rot fillColor: '#ef4444', // Rot fillOpacity: 0.1, // Sehr transparent (90% durchsichtig) weight: 2 // Linienstärke }); // Popup zeigt nur allgemeine Information cityPolygon.bindPopup(`
General Area:
${cityName}, ${county ? county + ', ' : ''}${state}
City boundary shown for privacy.
Exact location provided after contact.
`); ``` 4. **Address Control Override:** - Zeigt nur: "Austin, Travis County, TX" - **NICHT** angezeigt: Straßenname, Hausnummer, PLZ 5. **OpenStreetMap Link Override:** - Zoom-Level 11 (Stadt-Ansicht) statt Zoom-Level 15 (Straßen-Ansicht) - Keine Marker auf exakter Position **Entwicklungs-Verlauf (Entscheidungen):** | Ansatz | Grund für Ablehnung | Status | |---|---|---| | 2km Fuzzy-Radius | Zu klein - bei wenigen Businesses in Stadt identifizierbar | ❌ Abgelehnt | | County-Level | Zu groß - schlechte UX, schlechtes SEO | ❌ Abgelehnt | | Stadt-Center + 8km-Kreis | Funktioniert, aber nicht professionell aussehend | ⚠️ Fallback | | **Stadt-Grenz-Polygon (wie Google Maps)** | Professionell, präzise, gute Privacy | ✅ **Implementiert** | **Vorteile der Lösung:** - ✅ **Privacy by Design** - Exakte Location nicht sichtbar - ✅ **Professionelles Erscheinungsbild** - Wie Google Maps Stadt-Grenzen - ✅ **Genaue Stadt-Darstellung** - Nutzt offizielle OSM-Daten - ✅ **Robust** - Fallback auf Kreis bei API-Problemen - ✅ **SEO-freundlich** - Stadt-Namen bleiben in Metadaten erhalten - ✅ **Multi-Polygon Support** - Städte mit mehreren Bereichen (Inseln, etc.) **Was NICHT geändert wurde:** - ❌ **Commercial Property Listings** zeigen weiterhin exakte Adressen - ❌ **User/Professional Locations** zeigen weiterhin Stadt-Pins - ❌ **Datenbank** - Location-Daten bleiben unverändert gespeichert - ❌ **Backend** - Keine API-Änderungen nötig --- ## 4) SEO-Verbesserungen ### 4.1 Meta-Tags & Structured Data **Status:** ✅ Implementiert **Neue SEO-Features:** - ✅ Dynamische Title & Description für alle Listing-Seiten - ✅ Open Graph Tags für Social Media Sharing - ✅ JSON-LD Structured Data (Schema.org) - ✅ Canonical URLs - ✅ Noindex für 404-Seiten **Implementierung:** - `bizmatch/src/app/services/seo.service.ts` - Automatische Meta-Tag-Generierung basierend auf Listing-Daten ### 4.2 Sitemap-Generierung **Status:** ✅ Implementiert **Endpunkte:** - `/bizmatch/sitemap.xml` - Haupt-Sitemap (Index) - `/bizmatch/sitemap/static.xml` - Statische Seiten - `/bizmatch/sitemap/business-1.xml` - Business-Listings (paginiert) - `/bizmatch/sitemap/commercial-1.xml` - Commercial-Properties (paginiert) **Controller:** `bizmatch-server/src/sitemap/sitemap.controller.ts` ### 4.3 SEO-freundliche 404-Seite **Datei:** `bizmatch/src/app/components/not-found/not-found.component.ts` **Features:** - ✅ Breadcrumbs für bessere Navigation - ✅ `noindex` Meta-Tag (verhindert Indexierung) - ✅ Aussagekräftige Title & Description - ✅ Link zurück zur Homepage --- ## 5) Code-Cleanup & Wartung ### 5.1 Gelöschte temporäre Dateien **Datum:** November 2025 **Status:** ✅ Abgeschlossen **Markdown-Dokumentation (7 Dateien):** - ❌ `DATABASE-FIX-INSTRUCTIONS.md` - ❌ `DEPLOYMENT-GUIDE.md` - ❌ `PROFESSIONALS-TAB-IMPLEMENTATION.md` - ❌ `RESTART-BACKEND.md` - ❌ `SEO-IMPROVEMENTS-SUMMARY.md` - ❌ `bizmatch-server/SEO-DEPLOYMENT-SUCCESS.md` - ❌ `bizmatch-server/TYPESCRIPT-FIX-SUMMARY.md` **Shell-Scripts (33 Dateien):** - ❌ Alle `.sh`-Dateien in `bizmatch-server/` (check-*, fix-*, test-*, run-*, setup-*, etc.) **SQL-Test-Dateien (5 Dateien):** - ❌ `create-schema.sql` - ❌ `insert-professionals-json.sql` - ❌ `insert-professionals-simple.sql` - ❌ `insert-test-professionals.sql` - ❌ `insert-test-professionals-fixed.sql` **Debug-JavaScript (2 Dateien):** - ❌ `check-db.js` - ❌ `verify.js` **Komplette Verzeichnisse:** - ❌ `bizmatch-server/scripts/` (~13 Dateien) - ❌ `bizmatch-server/src/scripts/` (~13 Dateien) - ❌ `.claude/` (Verzeichnis) **Gesamt:** ~75 temporäre Dateien und 3 Verzeichnisse entfernt ### 5.2 Beibehaltene Konfigurationsdateien **✅ Wichtige Dateien (nicht gelöscht):** - `README.md` (Projekt-Dokumentation) - `bizmatch-server/README.md` (Server-Dokumentation) - `.eslintrc.js` (Code-Style-Konfiguration) - `docker-compose.yml` (Container-Konfiguration) - `.gitignore` (Git-Konfiguration) --- ## 6) Bekannte Issues & Workarounds ### 6.1 Docker-Container-Neustart nach Code-Änderungen **Problem:** TypeScript-Kompilierungsfehler können dazu führen, dass der Backend-Container nicht startet. **Lösung:** ```bash # Container-Logs prüfen docker logs bizmatch-app --tail 50 # Bei TypeScript-Fehlern: Container neu starten docker restart bizmatch-app # Prüfen, ob App erfolgreich gestartet ist docker logs bizmatch-app | grep "Nest application successfully started" ``` ### 6.2 Database Connection Issues **Problem:** `password authentication failed for user "bizmatch"` **Lösung:** Siehe [README.md - Abschnitt 4.1](README.md#41-password-authentication-failed-for-user-bizmatch) ### 6.3 Frontend Proxy-Fehler **Problem:** `http proxy error: /bizmatch/select-options` während Backend-Neustart **Lösung:** - Warten, bis Backend vollständig gestartet ist (~30 Sekunden) - Frontend-Dev-Server bei Bedarf neu starten: `npm start` --- ## Migration-Guide: JSON-Schema ### Von relationaler DB zu JSON-Schema **Beispiel: User-Daten** **Alt (relationale Tabelle):** ```sql CREATE TABLE users ( id UUID PRIMARY KEY, email VARCHAR(255), firstname VARCHAR(100), lastname VARCHAR(100), phone VARCHAR(50), ... ); ``` **Neu (JSON-Schema):** ```sql CREATE TABLE users_json ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email VARCHAR(255), data JSONB ); ``` **JSON-Struktur in `data`-Spalte:** ```json { "firstname": "John", "lastname": "Doe", "email": "john.doe@example.com", "phone": "+1-555-0123", "customerType": "professional", "customerSubType": "broker", "location": { "name": "Austin", "state": "TX", "latitude": 30.2672, "longitude": -97.7431 }, "showInDirectory": true, "created": "2025-11-25T10:30:00Z", "updated": "2025-11-25T10:30:00Z" } ``` **Query-Beispiele:** ```sql -- Alle Professionals in Texas finden SELECT * FROM users_json WHERE (data->>'customerType') = 'professional' AND (data->'location'->>'state') = 'TX'; -- Nach Name suchen SELECT * FROM users_json WHERE (data->>'firstname') ILIKE '%John%'; -- Radius-Suche (50 Meilen um Austin) SELECT * FROM users_json WHERE ( 3959 * 2 * ASIN(SQRT( POWER(SIN((30.2672 - (data->'location'->>'latitude')::float) * PI() / 180 / 2), 2) + COS(30.2672 * PI() / 180) * COS((data->'location'->>'latitude')::float * PI() / 180) * POWER(SIN((-97.7431 - (data->'location'->>'longitude')::float) * PI() / 180 / 2), 2) )) ) <= 50; ``` --- ## Support & Fragen Bei Fragen zu diesen Änderungen: 1. README.md für Setup-Informationen konsultieren 2. Docker-Logs prüfen: `docker logs bizmatch-app` und `docker logs bizmatchdb` 3. Git-History für Details zu Änderungen durchsuchen **Letzte Aktualisierung:** November 2025