21 KiB
Changelog - BizMatch Project
Dokumentation aller wichtigen Änderungen am BizMatch-Projekt. Diese Datei listet Feature-Implementierungen, Bugfixes, Datenbank-Migrationen und architektonische Verbesserungen auf.
Inhaltsverzeichnis
- Datenbank-Änderungen
- Backend-Änderungen
- Frontend-Änderungen
- SEO-Verbesserungen
- Code-Cleanup & Wartung
- 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,commercialsexistieren 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):
-- 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):
-- 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:
- Alte Tabellen existieren weiterhin - Sie wurden nicht gelöscht, um als Backup zu dienen
- Datenbank-Name - Weiterhin
bizmatch(keine Änderung) - Datenbank-User - Weiterhin
bizmatch(keine Änderung) - Datenbank-Passwort - Keine Änderung
- Datenbank-Port - Weiterhin
5432(keine Änderung) - Docker-Container-Name - Weiterhin
bizmatchdb(keine Änderung) - Indices - PostgreSQL JSONB-Indices wurden automatisch erstellt
Was wurde geändert
✅ Folgende Dinge wurden geändert:
-
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.)
- Backend liest/schreibt nur noch in
-
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.
locationmitname,state,latitude,longitude) - Arrays direkt im JSON speicherbar (z.B.
imageOrder,areasServed)
- Alle Felder, die vorher einzelne Spalten waren, sind jetzt in der
-
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
- Statt
Beispiel: User-Datensatz Vorher/Nachher
VORHER (relationale Tabelle users):
| id | 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 | 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:
# 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 (nichtareasServed-Array) - Struktur:
{ name: 'Austin', state: 'TX', latitude: 30.2672, longitude: -97.7431 }
Betroffene Queries
- Exact City Search:
location.nameILIKE-Vergleich - Radius Search: Haversine-Formel mit
location.latitudeundlocation.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):
if (criteria.city && criteria.searchType === 'exact') {
whereConditions.push(sql`(${schema.users_json.data}->'location'->>'name') ILIKE ${criteria.city.name}`);
}
Radius Search (Zeile 32-36):
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):
if (criteria.state) {
whereConditions.push(sql`(${schema.users_json.data}->'location'->>'state') = ${criteria.state}`);
}
County Filter (Zeile 51-53):
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-salestatt/business/uuid-123-456/commercial-property/downtown-retail-space-dallasstatt/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
50Meilen 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):
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.tsdetails-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):
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 MethodegetCityBoundary()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:
- Nominatim API Integration (geo.service.ts:33-37):
getCityBoundary(cityName: string, state: string): Observable<any> {
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 });
}
- City Boundary Polygon (details-business-listing.component.ts:322-430):
- Lädt Stadt-Grenz-Polygon von OpenStreetMap Nominatim API
- Zeigt rote Umrandung der Stadt (wie Google Maps)
- Unterstützt
PolygonundMultiPolygonGeometrien - Fallback: 8km-Radius-Kreis bei API-Fehler oder fehlenden Daten
- Karten-Konfiguration:
// 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(`
<div style="padding: 8px;">
<strong>General Area:</strong><br/>
${cityName}, ${county ? county + ', ' : ''}${state}<br/>
<small style="color: #666;">City boundary shown for privacy.<br/>Exact location provided after contact.</small>
</div>
`);
- Address Control Override:
- Zeigt nur: "Austin, Travis County, TX"
- NICHT angezeigt: Straßenname, Hausnummer, PLZ
- 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
- ✅
noindexMeta-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 inbizmatch-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:
# 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
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):
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255),
firstname VARCHAR(100),
lastname VARCHAR(100),
phone VARCHAR(50),
...
);
Neu (JSON-Schema):
CREATE TABLE users_json (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255),
data JSONB
);
JSON-Struktur in data-Spalte:
{
"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:
-- 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:
- README.md für Setup-Informationen konsultieren
- Docker-Logs prüfen:
docker logs bizmatch-appunddocker logs bizmatchdb - Git-History für Details zu Änderungen durchsuchen
Letzte Aktualisierung: November 2025