Compare commits

..

No commits in common. "79098f59c64b95173f6892592d62a2af94bbb502" and "0b4e4207d1d3e7eb00cb370f491f7bd05b601b46" have entirely different histories.

19 changed files with 144 additions and 239 deletions

View File

@ -1,6 +0,0 @@
node_modules
.git
.idea
.vscode
dist
coverage

View File

@ -1,25 +1,19 @@
# --- STAGE 1: Build --- # Build Stage
FROM node:22-alpine AS builder FROM node:18-alpine AS build
WORKDIR /app WORKDIR /app
# HIER KEIN NODE_ENV=production setzen! Wir brauchen devDependencies zum Bauen.
COPY package*.json ./ COPY package*.json ./
RUN npm ci RUN npm install
COPY . . COPY . .
RUN npm run build RUN npm run build
# --- STAGE 2: Runtime --- # Runtime Stage
FROM node:22-alpine FROM node:18-alpine
WORKDIR /app WORKDIR /app
COPY --from=build /app/dist /app/dist
COPY --from=build /app/package*.json /app/
# HIER ist es richtig! RUN npm install --production
ENV NODE_ENV=production
COPY --from=builder /app/dist /app/dist CMD ["node", "dist/main.js"]
COPY --from=builder /app/package*.json /app/
# Installiert nur "dependencies" (Nest core, TypeORM, Helmet, Sharp etc.)
# "devDependencies" (TypeScript, Jest, ESLint) werden weggelassen.
RUN npm ci --omit=dev
# WICHTIG: Pfad prüfen (siehe Punkt 2 unten)
CMD ["node", "dist/src/main.js"]

View File

@ -0,0 +1,48 @@
services:
app:
image: node:22-alpine
container_name: bizmatch-app
working_dir: /app
volumes:
- ./:/app
- node_modules:/app/node_modules
ports:
- '3001:3001'
env_file:
- .env
environment:
- NODE_ENV=development
- DATABASE_URL
command: sh -c "if [ ! -f node_modules/.installed ]; then npm ci && touch node_modules/.installed; fi && npm run build && node dist/src/main.js"
restart: unless-stopped
depends_on:
- postgres
networks:
- bizmatch
postgres:
container_name: bizmatchdb
image: postgres:17-alpine
restart: unless-stopped
volumes:
- bizmatch-db-data:/var/lib/postgresql/data
env_file:
- .env
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- '5434:5432'
networks:
- bizmatch
volumes:
bizmatch-db-data:
driver: local
node_modules:
driver: local
networks:
bizmatch:
external: true

View File

@ -1,41 +1,13 @@
# STAGE 1: Build
FROM node:22-alpine AS builder
# Wir erstellen ein Arbeitsverzeichnis, das eine Ebene über dem Projekt liegt
WORKDIR /usr/src/app
# 1. Wir kopieren die Backend-Models an die Stelle, wo Angular sie erwartet
# Deine Pfade suchen nach ../bizmatch-server, also legen wir es daneben.
COPY bizmatch-server/src/models ./bizmatch-server/src/models
# 2. Jetzt kümmern wir uns um das Frontend
# Wir kopieren erst die package Files für besseres Caching
COPY bizmatch/package*.json ./bizmatch/
# Wechseln in den Frontend Ordner zum Installieren
WORKDIR /usr/src/app/bizmatch
RUN npm ci
# 3. Den Rest des Frontends kopieren
COPY bizmatch/ .
# 4. Bauen
RUN npm run build:ssr
# --- STAGE 2: Runtime ---
FROM node:22-alpine FROM node:22-alpine
WORKDIR /app WORKDIR /app
ENV NODE_ENV=production # GANZEN dist-Ordner kopieren, nicht nur bizmatch
ENV PORT=4000 COPY dist ./dist
COPY package*.json ./
# Kopiere das Ergebnis aus dem Builder (Pfad beachten!)
COPY --from=builder /usr/src/app/bizmatch/dist /app/dist
COPY --from=builder /usr/src/app/bizmatch/package*.json /app/
RUN npm ci --omit=dev RUN npm ci --omit=dev
EXPOSE 4000 EXPOSE 4200
CMD ["node", "dist/bizmatch/server/server.mjs"] CMD ["node", "dist/bizmatch/server/server.mjs"]

View File

@ -53,10 +53,7 @@
], ],
"styles": [ "styles": [
"src/styles.scss", "src/styles.scss",
"src/styles/lazy-load.css", "src/styles/lazy-load.css"
"node_modules/quill/dist/quill.snow.css",
"node_modules/leaflet/dist/leaflet.css",
"node_modules/ngx-sharebuttons/themes/default.scss"
] ]
}, },
"configurations": { "configurations": {
@ -101,8 +98,7 @@
], ],
"optimization": true, "optimization": true,
"extractLicenses": false, "extractLicenses": false,
"sourceMap": true, "sourceMap": true
"outputHashing": "all"
} }
}, },
"defaultConfiguration": "production" "defaultConfiguration": "production"

View File

@ -0,0 +1,10 @@
services:
bizmatch-ssr:
build: .
image: bizmatch-ssr
container_name: bizmatch-ssr
restart: unless-stopped
ports:
- '4200:4200'
environment:
NODE_ENV: DEVELOPMENT

View File

@ -7,10 +7,6 @@ import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@a
import { initializeApp, provideFirebaseApp } from '@angular/fire/app'; import { initializeApp, provideFirebaseApp } from '@angular/fire/app';
import { getAuth, provideAuth } from '@angular/fire/auth'; import { getAuth, provideAuth } from '@angular/fire/auth';
import { provideAnimations } from '@angular/platform-browser/animations'; import { provideAnimations } from '@angular/platform-browser/animations';
import { GALLERY_CONFIG, GalleryConfig } from 'ng-gallery';
import { provideQuillConfig } from 'ngx-quill';
import { provideShareButtonsOptions, SharerMethods, withConfig } from 'ngx-sharebuttons';
import { shareIcons } from 'ngx-sharebuttons/icons';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import { routes } from './app.routes'; import { routes } from './app.routes';
import { AuthInterceptor } from './interceptors/auth.interceptor'; import { AuthInterceptor } from './interceptors/auth.interceptor';
@ -24,8 +20,7 @@ import { createLogger } from './utils/utils';
const logger = createLogger('ApplicationConfig'); const logger = createLogger('ApplicationConfig');
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
// Temporarily disabled for SSR debugging provideClientHydration(),
// provideClientHydration(),
provideHttpClient(withInterceptorsFromDi()), provideHttpClient(withInterceptorsFromDi()),
{ {
provide: APP_INITIALIZER, provide: APP_INITIALIZER,
@ -48,13 +43,6 @@ export const appConfig: ApplicationConfig = {
provide: 'TIMEOUT_DURATION', provide: 'TIMEOUT_DURATION',
useValue: 5000, // Standard-Timeout von 5 Sekunden useValue: 5000, // Standard-Timeout von 5 Sekunden
}, },
{
provide: GALLERY_CONFIG,
useValue: {
autoHeight: true,
imageSize: 'cover',
} as GalleryConfig,
},
{ provide: ErrorHandler, useClass: GlobalErrorHandler }, // Registriere den globalen ErrorHandler { provide: ErrorHandler, useClass: GlobalErrorHandler }, // Registriere den globalen ErrorHandler
{ {
provide: IMAGE_CONFIG, provide: IMAGE_CONFIG,
@ -62,13 +50,6 @@ export const appConfig: ApplicationConfig = {
disableImageSizeWarning: true, disableImageSizeWarning: true,
}, },
}, },
provideShareButtonsOptions(
shareIcons(),
withConfig({
debug: true,
sharerMethod: SharerMethods.Anchor,
}),
),
provideRouter( provideRouter(
routes, routes,
withEnabledBlockingInitialNavigation(), withEnabledBlockingInitialNavigation(),
@ -79,18 +60,6 @@ export const appConfig: ApplicationConfig = {
), ),
...(environment.production ? [POSTHOG_INIT_PROVIDER] : []), ...(environment.production ? [POSTHOG_INIT_PROVIDER] : []),
provideAnimations(), provideAnimations(),
provideQuillConfig({
modules: {
syntax: true,
toolbar: [
['bold', 'italic', 'underline'], // Einige Standardoptionen
[{ header: [1, 2, 3, false] }], // Benutzerdefinierte Header
[{ list: 'ordered' }, { list: 'bullet' }],
[{ color: [] }], // Dropdown mit Standardfarben
['clean'], // Entfernt Formatierungen
],
},
}),
provideFirebaseApp(() => initializeApp(environment.firebaseConfig)), provideFirebaseApp(() => initializeApp(environment.firebaseConfig)),
provideAuth(() => getAuth()), provideAuth(() => getAuth()),
], ],

View File

@ -1,24 +1,8 @@
import { RenderMode, ServerRoute } from '@angular/ssr'; import { RenderMode, ServerRoute } from '@angular/ssr';
export const serverRoutes: ServerRoute[] = [ export const serverRoutes: ServerRoute[] = [
{ path: 'home', renderMode: RenderMode.Server }, // Das hatten wir vorhin gefixt { path: 'home', renderMode: RenderMode.Prerender },
// WICHTIG: Alle geschützten Routen nur im Browser rendern!
// Damit überspringt der Server den AuthGuard Check komplett und schickt
// nur eine leere Hülle (index.html), die der Browser dann füllt.
{ path: 'account', renderMode: RenderMode.Client },
{ path: 'account/**', renderMode: RenderMode.Client },
{ path: 'myListings', renderMode: RenderMode.Client },
{ path: 'myFavorites', renderMode: RenderMode.Client },
{ path: 'createBusinessListing', renderMode: RenderMode.Client },
{ path: 'createCommercialPropertyListing', renderMode: RenderMode.Client },
{ path: 'editBusinessListing/**', renderMode: RenderMode.Client },
{ path: 'editCommercialPropertyListing/**', renderMode: RenderMode.Client },
// Statische Seiten
{ path: 'terms-of-use', renderMode: RenderMode.Prerender }, { path: 'terms-of-use', renderMode: RenderMode.Prerender },
{ path: 'privacy-statement', renderMode: RenderMode.Prerender }, { path: 'privacy-statement', renderMode: RenderMode.Prerender },
// Fallback
{ path: '**', renderMode: RenderMode.Server } { path: '**', renderMode: RenderMode.Server }
]; ];

View File

@ -1,27 +1,15 @@
import { Injectable, PLATFORM_ID, Inject } from '@angular/core'; import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router'; import { CanActivate, Router } from '@angular/router';
import { AuthService } from '../services/auth.service'; import { AuthService } from '../services/auth.service';
import { isPlatformBrowser } from '@angular/common'; import { createLogger } from '../utils/utils';
const logger = createLogger('AuthGuard');
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class AuthGuard implements CanActivate { export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
constructor(
private authService: AuthService,
private router: Router,
@Inject(PLATFORM_ID) private platformId: Object
) {}
async canActivate(): Promise<boolean> { async canActivate(): Promise<boolean> {
// 1. SSR CHECK: Wenn wir auf dem Server sind, immer erlauben!
// Der Server soll nicht redirecten, sondern einfach das HTML rendern.
if (!isPlatformBrowser(this.platformId)) {
return true;
}
// 2. CLIENT CHECK: Das läuft nur im Browser
const token = await this.authService.getToken(); const token = await this.authService.getToken();
if (token) { if (token) {
return true; return true;

View File

@ -15,7 +15,7 @@
<!-- SEO-optimized heading --> <!-- SEO-optimized heading -->
<div class="mb-6"> <div class="mb-6">
<h1 class="text-3xl md:text-4xl font-bold text-neutral-900 mb-2">Businesses for Sale - Find Your Next Business Opportunity</h1> <h1 class="text-3xl md:text-4xl font-bold text-neutral-900 mb-2">Businesses for Sale - Find Your Next Business Opportunity</h1>
<p class="text-lg text-neutral-600">Discover profitable business opportunities across the State of Texas. Browse <p class="text-lg text-neutral-600">Discover profitable business opportunities across the United States. Browse
verified listings from business owners and brokers.</p> verified listings from business owners and brokers.</p>
<div class="mt-4 text-base text-neutral-700 max-w-4xl"> <div class="mt-4 text-base text-neutral-700 max-w-4xl">
<p>BizMatch features a curated selection of businesses for sale across diverse industries and price ranges. Browse opportunities in sectors like restaurants, retail, franchises, services, e-commerce, and manufacturing. Each listing includes financial details, years established, location information, and seller contact details. Our marketplace connects business buyers with sellers and brokers nationwide, making it easy to find your next business opportunity.</p> <p>BizMatch features a curated selection of businesses for sale across diverse industries and price ranges. Browse opportunities in sectors like restaurants, retail, franchises, services, e-commerce, and manufacturing. Each listing includes financial details, years established, location information, and seller contact details. Our marketplace connects business buyers with sellers and brokers nationwide, making it easy to find your next business opportunity.</p>

View File

@ -129,16 +129,23 @@
mask="(000) 000-0000"></app-validated-input> mask="(000) 000-0000"></app-validated-input>
<app-validated-input label="Company Website" name="companyWebsite" <app-validated-input label="Company Website" name="companyWebsite"
[(ngModel)]="user.companyWebsite"></app-validated-input> [(ngModel)]="user.companyWebsite"></app-validated-input>
<!-- <app-validated-input label="Company Location" name="companyLocation" [(ngModel)]="user.companyLocation"></app-validated-input> -->
<!-- <app-validated-city label="Company Location" name="location" [(ngModel)]="user.location"></app-validated-city> -->
<app-validated-location label="Company Location" name="location" <app-validated-location label="Company Location" name="location"
[(ngModel)]="user.location"></app-validated-location> [(ngModel)]="user.location"></app-validated-location>
</div> </div>
<!-- <div>
<label for="companyOverview" class="block text-sm font-medium text-gray-700">Company Overview</label>
<quill-editor [(ngModel)]="user.companyOverview" name="companyOverview" [modules]="quillModules"></quill-editor>
</div> -->
<div> <div>
<app-validated-quill label="Company Overview" name="companyOverview" <app-validated-quill label="Company Overview" name="companyOverview"
[(ngModel)]="user.companyOverview"></app-validated-quill> [(ngModel)]="user.companyOverview"></app-validated-quill>
</div> </div>
<div> <div>
<!-- <label for="offeredServices" class="block text-sm font-medium text-gray-700">Services We Offer</label>
<quill-editor [(ngModel)]="user.offeredServices" name="offeredServices" [modules]="quillModules"></quill-editor> -->
<app-validated-quill label="Services We Offer" name="offeredServices" <app-validated-quill label="Services We Offer" name="offeredServices"
[(ngModel)]="user.offeredServices"></app-validated-quill> [(ngModel)]="user.offeredServices"></app-validated-quill>
</div> </div>

View File

@ -3,7 +3,7 @@ import { ChangeDetectorRef, Component } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { APP_ICONS } from '../../../utils/fontawesome-icons'; import { APP_ICONS } from '../../../utils/fontawesome-icons';
import { NgSelectModule } from '@ng-select/ng-select'; import { NgSelectModule } from '@ng-select/ng-select';
import { QuillModule } from 'ngx-quill'; import { QuillModule, provideQuillConfig } from 'ngx-quill';
import { lastValueFrom } from 'rxjs'; import { lastValueFrom } from 'rxjs';
import { User } from '../../../../../../bizmatch-server/src/models/db.model'; import { User } from '../../../../../../bizmatch-server/src/models/db.model';
import { AutoCompleteCompleteEvent, Invoice, UploadParams, ValidationMessage, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model'; import { AutoCompleteCompleteEvent, Invoice, UploadParams, ValidationMessage, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
@ -47,7 +47,19 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
], ],
providers: [ providers: [
TitleCasePipe, TitleCasePipe,
DatePipe DatePipe,
provideQuillConfig({
modules: {
syntax: true,
toolbar: [
['bold', 'italic', 'underline'],
[{ header: [1, 2, 3, false] }],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ color: [] }],
['clean'],
],
},
}) as any,
], ],
templateUrl: './account.component.html', templateUrl: './account.component.html',
styleUrls: [ styleUrls: [
@ -65,6 +77,9 @@ export class AccountComponent {
editorModules = TOOLBAR_OPTIONS; editorModules = TOOLBAR_OPTIONS;
env = environment; env = environment;
faTrash = APP_ICONS.faTrash; faTrash = APP_ICONS.faTrash;
quillModules = {
toolbar: [['bold', 'italic', 'underline', 'strike'], [{ list: 'ordered' }, { list: 'bullet' }], [{ header: [1, 2, 3, 4, 5, 6, false] }], [{ color: [] }, { background: [] }], ['clean']],
};
uploadParams: UploadParams; uploadParams: UploadParams;
validationMessages: ValidationMessage[] = []; validationMessages: ValidationMessage[] = [];
customerTypeOptions: Array<{ value: string; label: string }> = []; customerTypeOptions: Array<{ value: string; label: string }> = [];

View File

@ -11,6 +11,7 @@ import { QuillModule } from 'ngx-quill';
import { NgSelectModule } from '@ng-select/ng-select'; import { NgSelectModule } from '@ng-select/ng-select';
import { NgxCurrencyDirective } from 'ngx-currency'; import { NgxCurrencyDirective } from 'ngx-currency';
import { provideQuillConfig } from 'ngx-quill';
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model'; import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
import { AutoCompleteCompleteEvent, ImageProperty, createDefaultBusinessListing, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model'; import { AutoCompleteCompleteEvent, ImageProperty, createDefaultBusinessListing, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
@ -47,6 +48,20 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
ValidatedTextareaComponent, ValidatedTextareaComponent,
ValidatedLocationComponent, ValidatedLocationComponent,
], ],
providers: [
provideQuillConfig({
modules: {
syntax: true,
toolbar: [
['bold', 'italic', 'underline'],
[{ header: [1, 2, 3, false] }],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ color: [] }],
['clean'],
],
},
}) as any,
],
templateUrl: './edit-business-listing.component.html', templateUrl: './edit-business-listing.component.html',
styleUrls: [ styleUrls: [
'./edit-business-listing.component.scss', './edit-business-listing.component.scss',

View File

@ -11,7 +11,7 @@ import { APP_ICONS } from '../../../utils/fontawesome-icons';
import { NgSelectModule } from '@ng-select/ng-select'; import { NgSelectModule } from '@ng-select/ng-select';
import { NgxCurrencyDirective } from 'ngx-currency'; import { NgxCurrencyDirective } from 'ngx-currency';
import { ImageCropperComponent } from 'ngx-image-cropper'; import { ImageCropperComponent } from 'ngx-image-cropper';
import { QuillModule } from 'ngx-quill'; import { QuillModule, provideQuillConfig } from 'ngx-quill';
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model'; import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
import { AutoCompleteCompleteEvent, ImageProperty, UploadParams, createDefaultCommercialPropertyListing, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model'; import { AutoCompleteCompleteEvent, ImageProperty, UploadParams, createDefaultCommercialPropertyListing, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
@ -53,6 +53,20 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
ValidatedLocationComponent, ValidatedLocationComponent,
ImageCropAndUploadComponent, ImageCropAndUploadComponent,
], ],
providers: [
provideQuillConfig({
modules: {
syntax: true,
toolbar: [
['bold', 'italic', 'underline'],
[{ header: [1, 2, 3, false] }],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ color: [] }],
['clean'],
],
},
}) as any,
],
templateUrl: './edit-commercial-property-listing.component.html', templateUrl: './edit-commercial-property-listing.component.html',
styleUrls: [ styleUrls: [
'./edit-commercial-property-listing.component.scss', './edit-commercial-property-listing.component.scss',

View File

@ -1,20 +1,8 @@
// SSR-safe: check if window exists // SSR-safe: check if window exists (it doesn't on server-side)
// Im Browser nehmen wir den aktuellen Host (z.B. localhost). const hostname = typeof window !== 'undefined' ? window.location.hostname : 'localhost';
// Auf dem Server (SSR in Docker) nehmen wir 'bizmatch-app' (der Name des Backend-Containers).
const isBrowser = typeof window !== 'undefined' && window.navigator.userAgent !== 'node';
const hostname = isBrowser ? window.location.hostname : 'bizmatch-app';
// Im Server-Modus nutzen wir den internen Docker-Namen
const internalUrl = 'http://bizmatch-app:3001';
// Im Browser-Modus die öffentliche URL
const publicUrl = 'https://api.bizmatch.net';
const calculatedApiBaseUrl = isBrowser ? publicUrl : internalUrl;
// WICHTIG: Port anpassen!
// Lokal läuft das Backend auf 3001. Im Docker Container auch auf 3001.
// Deine alte Config hatte hier :4200 stehen, das war falsch (das ist das Frontend).
export const environment_base = { export const environment_base = {
// apiBaseUrl: 'http://localhost:3000', // apiBaseUrl: 'http://localhost:3000',
// GETTER FUNCTION für die API URL (besser als statischer String für diesen Fall) apiBaseUrl: `http://${hostname}:4200`,
apiBaseUrl: calculatedApiBaseUrl,
imageBaseUrl: 'https://dev.bizmatch.net', imageBaseUrl: 'https://dev.bizmatch.net',
buildVersion: '<BUILD_VERSION>', buildVersion: '<BUILD_VERSION>',
mailinfoUrl: 'https://dev.bizmatch.net', mailinfoUrl: 'https://dev.bizmatch.net',

View File

@ -1,15 +1,10 @@
import { environment_base } from './environment.base'; import { environment_base } from './environment.base';
export const environment = { ...environment_base }; // Kopie erstellen export const environment = environment_base;
environment.production = true; environment.production = true;
// WICHTIG: Diese Zeile auskommentieren, solange du lokal testest!
// Sonst greift er immer aufs Internet zu, statt auf deinen lokalen Docker-Container.
environment.apiBaseUrl = 'https://api.bizmatch.net'; environment.apiBaseUrl = 'https://api.bizmatch.net';
environment.mailinfoUrl = 'https://www.bizmatch.net'; environment.mailinfoUrl = 'https://www.bizmatch.net';
environment.imageBaseUrl = 'https://www.bizmatch.net';// Ggf. auch auskommentieren, wenn Bilder lokal liegen environment.imageBaseUrl = 'https://api.bizmatch.net';
environment.POSTHOG_KEY = 'phc_eUIcIq0UPVzEDtZLy78klKhGudyagBz3goDlKx8SQFe'; environment.POSTHOG_KEY = 'phc_eUIcIq0UPVzEDtZLy78klKhGudyagBz3goDlKx8SQFe';
environment.POSTHOG_HOST = 'https://eu.i.posthog.com'; environment.POSTHOG_HOST = 'https://eu.i.posthog.com';

View File

@ -7,7 +7,6 @@
// External CSS imports - these URL imports don't trigger deprecation warnings // External CSS imports - these URL imports don't trigger deprecation warnings
// Using css2 API with specific weights for better performance // Using css2 API with specific weights for better performance
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap');
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css');
// Local CSS files loaded as CSS (not SCSS) to avoid @import deprecation // Local CSS files loaded as CSS (not SCSS) to avoid @import deprecation
// Note: These are loaded via angular.json styles array is the preferred approach, // Note: These are loaded via angular.json styles array is the preferred approach,

View File

@ -1,62 +0,0 @@
services:
# --- FRONTEND ---
bizmatch-ssr:
build:
context: . # Pfad zum Angular Ordner
dockerfile: bizmatch/Dockerfile
image: bizmatch-ssr
container_name: bizmatch-ssr
extra_hosts:
- "localhost:host-gateway"
restart: unless-stopped
ports:
- '4200:4000' # Extern 4200 -> Intern 4000 (SSR)
environment:
NODE_ENV: production
volumes:
- ./bizmatch-server/pictures:/app/pictures
# --- BACKEND ---
app:
build:
context: ./bizmatch-server # Pfad zum NestJS Ordner
dockerfile: Dockerfile
image: bizmatch-server:latest
container_name: bizmatch-app
restart: unless-stopped
ports:
- '3001:3001'
env_file:
- ./bizmatch-server/.env # Pfad zur .env Datei
depends_on:
- postgres
networks:
- bizmatch
# WICHTIG: Kein Volume Mapping für node_modules im Prod-Modus!
# Das Image bringt alles fertig mit.
# --- DATABASE ---
postgres:
container_name: bizmatchdb
image: postgres:17-alpine
restart: unless-stopped
volumes:
- bizmatch-db-data:/var/lib/postgresql/data
env_file:
- ./bizmatch-server/.env
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- '5434:5432'
networks:
- bizmatch
volumes:
bizmatch-db-data:
driver: local
networks:
bizmatch:
external: false # Oder true, falls du es manuell erstellt hast

View File

@ -1,21 +0,0 @@
#!/bin/bash
echo "🚀 Starte Update Prozess..."
# 1. Neuesten Code holen
echo "📥 Git Pull..."
git pull
# 2. Docker Images neu bauen und Container neu starten
# --build: Zwingt Docker, die Images neu zu bauen (nutzt Multi-Stage)
# -d: Detached mode (im Hintergrund)
# --remove-orphans: Räumt alte Container auf, falls Services umbenannt wurden
echo "🐳 Baue und starte Container..."
docker compose up -d --build --remove-orphans
# 3. Aufräumen (Optional)
# Löscht alte Images ("dangling images"), die beim Build übrig geblieben sind, um Platz zu sparen
echo "🧹 Räume alte Images auf..."
docker image prune -f
echo "✅ Fertig! Die App läuft in der neuesten Version."