Compare commits
15 Commits
0b4e4207d1
...
timo-clean
| Author | SHA1 | Date | |
|---|---|---|---|
| fca746cef6 | |||
| 79098f59c6 | |||
| 345761da87 | |||
| 7e00b4d71b | |||
| 715220f6d5 | |||
| dc79ac3df7 | |||
| a545b84f6c | |||
| 2d293d8b12 | |||
| d008b50892 | |||
| 1a1eaa46ae | |||
| a9dcb66e5b | |||
| 33ea71dc12 | |||
| 91bcf3c2ed | |||
| 36ef7eb4bf | |||
| ae12eb87f0 |
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
.git
|
||||
.idea
|
||||
.vscode
|
||||
dist
|
||||
coverage
|
||||
@@ -1,19 +1,25 @@
|
||||
# Build Stage
|
||||
FROM node:18-alpine AS build
|
||||
|
||||
# --- STAGE 1: Build ---
|
||||
FROM node:22-alpine AS builder
|
||||
WORKDIR /app
|
||||
# HIER KEIN NODE_ENV=production setzen! Wir brauchen devDependencies zum Bauen.
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Runtime Stage
|
||||
FROM node:18-alpine
|
||||
|
||||
# --- STAGE 2: Runtime ---
|
||||
FROM node:22-alpine
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/dist /app/dist
|
||||
COPY --from=build /app/package*.json /app/
|
||||
|
||||
RUN npm install --production
|
||||
# HIER ist es richtig!
|
||||
ENV NODE_ENV=production
|
||||
|
||||
CMD ["node", "dist/main.js"]
|
||||
COPY --from=builder /app/dist /app/dist
|
||||
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"]
|
||||
@@ -1,48 +0,0 @@
|
||||
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
|
||||
@@ -1,13 +1,41 @@
|
||||
# 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
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# GANZEN dist-Ordner kopieren, nicht nur bizmatch
|
||||
COPY dist ./dist
|
||||
COPY package*.json ./
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=4000
|
||||
|
||||
# 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
|
||||
|
||||
EXPOSE 4200
|
||||
EXPOSE 4000
|
||||
|
||||
CMD ["node", "dist/bizmatch/server/server.mjs"]
|
||||
|
||||
@@ -53,7 +53,10 @@
|
||||
],
|
||||
"styles": [
|
||||
"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": {
|
||||
@@ -98,7 +101,8 @@
|
||||
],
|
||||
"optimization": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
"sourceMap": true,
|
||||
"outputHashing": "all"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
services:
|
||||
bizmatch-ssr:
|
||||
build: .
|
||||
image: bizmatch-ssr
|
||||
container_name: bizmatch-ssr
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '4200:4200'
|
||||
environment:
|
||||
NODE_ENV: DEVELOPMENT
|
||||
@@ -7,6 +7,10 @@ import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@a
|
||||
import { initializeApp, provideFirebaseApp } from '@angular/fire/app';
|
||||
import { getAuth, provideAuth } from '@angular/fire/auth';
|
||||
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 { routes } from './app.routes';
|
||||
import { AuthInterceptor } from './interceptors/auth.interceptor';
|
||||
@@ -20,7 +24,8 @@ import { createLogger } from './utils/utils';
|
||||
const logger = createLogger('ApplicationConfig');
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideClientHydration(),
|
||||
// Temporarily disabled for SSR debugging
|
||||
// provideClientHydration(),
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
@@ -43,6 +48,13 @@ export const appConfig: ApplicationConfig = {
|
||||
provide: 'TIMEOUT_DURATION',
|
||||
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: IMAGE_CONFIG,
|
||||
@@ -50,6 +62,13 @@ export const appConfig: ApplicationConfig = {
|
||||
disableImageSizeWarning: true,
|
||||
},
|
||||
},
|
||||
provideShareButtonsOptions(
|
||||
shareIcons(),
|
||||
withConfig({
|
||||
debug: true,
|
||||
sharerMethod: SharerMethods.Anchor,
|
||||
}),
|
||||
),
|
||||
provideRouter(
|
||||
routes,
|
||||
withEnabledBlockingInitialNavigation(),
|
||||
@@ -60,6 +79,18 @@ export const appConfig: ApplicationConfig = {
|
||||
),
|
||||
...(environment.production ? [POSTHOG_INIT_PROVIDER] : []),
|
||||
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)),
|
||||
provideAuth(() => getAuth()),
|
||||
],
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
import { RenderMode, ServerRoute } from '@angular/ssr';
|
||||
|
||||
export const serverRoutes: ServerRoute[] = [
|
||||
{ path: 'home', renderMode: RenderMode.Prerender },
|
||||
{ path: 'home', renderMode: RenderMode.Server }, // Das hatten wir vorhin gefixt
|
||||
|
||||
// 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: 'privacy-statement', renderMode: RenderMode.Prerender },
|
||||
|
||||
// Fallback
|
||||
{ path: '**', renderMode: RenderMode.Server }
|
||||
];
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, PLATFORM_ID, Inject } from '@angular/core';
|
||||
import { CanActivate, Router } from '@angular/router';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
import { createLogger } from '../utils/utils';
|
||||
const logger = createLogger('AuthGuard');
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
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> {
|
||||
// 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();
|
||||
if (token) {
|
||||
return true;
|
||||
|
||||
@@ -136,44 +136,13 @@ export class HomeComponent {
|
||||
// FAQ content is preserved in component for future use when FAQ section is made visible
|
||||
const organizationSchema = this.seoService.generateOrganizationSchema();
|
||||
|
||||
// Add HowTo schema for buying a business
|
||||
const howToSchema = this.seoService.generateHowToSchema({
|
||||
name: 'How to Buy a Business on BizMatch',
|
||||
description: 'Step-by-step guide to finding and purchasing your ideal business through BizMatch marketplace',
|
||||
totalTime: 'PT45M',
|
||||
steps: [
|
||||
{
|
||||
name: 'Browse Business Listings',
|
||||
text: 'Search through thousands of verified business listings using our advanced filters. Filter by industry, location, price range, revenue, and more to find businesses that match your criteria.'
|
||||
},
|
||||
{
|
||||
name: 'Review Business Details',
|
||||
text: 'Examine the business financials, including annual revenue, cash flow, asking price, and years established. Read the detailed business description and view photos of the operation.'
|
||||
},
|
||||
{
|
||||
name: 'Contact the Seller',
|
||||
text: 'Use our secure messaging system to contact the seller or business broker directly. Request additional information, financial documents, or schedule a site visit to see the business in person.'
|
||||
},
|
||||
{
|
||||
name: 'Conduct Due Diligence',
|
||||
text: 'Review all financial statements, tax returns, lease agreements, and legal documents. Verify the business information, inspect the physical location, and consult with legal and financial advisors.'
|
||||
},
|
||||
{
|
||||
name: 'Make an Offer',
|
||||
text: 'Submit a formal offer based on your valuation and due diligence findings. Negotiate terms including purchase price, payment structure, transition period, and any contingencies.'
|
||||
},
|
||||
{
|
||||
name: 'Close the Transaction',
|
||||
text: 'Work with attorneys and escrow services to finalize all legal documents, transfer ownership, and complete the purchase. The seller will transfer assets, train you on operations, and help with the transition.'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
// Add SearchBox schema for Sitelinks Search
|
||||
const searchBoxSchema = this.seoService.generateSearchBoxSchema();
|
||||
|
||||
// Inject schemas (FAQ schema excluded - content not visible to users)
|
||||
this.seoService.injectMultipleSchemas([organizationSchema, howToSchema, searchBoxSchema]);
|
||||
this.seoService.injectMultipleSchemas([organizationSchema, searchBoxSchema]);
|
||||
|
||||
// Clear all filters and sort options on initial load
|
||||
this.filterStateService.resetCriteria('businessListings');
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<!-- SEO-optimized heading -->
|
||||
<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>
|
||||
<p class="text-lg text-neutral-600">Discover profitable business opportunities across the United States. Browse
|
||||
<p class="text-lg text-neutral-600">Discover profitable business opportunities across the State of Texas. Browse
|
||||
verified listings from business owners and brokers.</p>
|
||||
<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>
|
||||
|
||||
@@ -129,23 +129,16 @@
|
||||
mask="(000) 000-0000"></app-validated-input>
|
||||
<app-validated-input label="Company Website" name="companyWebsite"
|
||||
[(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"
|
||||
[(ngModel)]="user.location"></app-validated-location>
|
||||
</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>
|
||||
<app-validated-quill label="Company Overview" name="companyOverview"
|
||||
[(ngModel)]="user.companyOverview"></app-validated-quill>
|
||||
</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"
|
||||
[(ngModel)]="user.offeredServices"></app-validated-quill>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ChangeDetectorRef, Component } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { APP_ICONS } from '../../../utils/fontawesome-icons';
|
||||
import { NgSelectModule } from '@ng-select/ng-select';
|
||||
import { QuillModule, provideQuillConfig } from 'ngx-quill';
|
||||
import { QuillModule } from 'ngx-quill';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
import { AutoCompleteCompleteEvent, Invoice, UploadParams, ValidationMessage, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
@@ -47,19 +47,7 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||
],
|
||||
providers: [
|
||||
TitleCasePipe,
|
||||
DatePipe,
|
||||
provideQuillConfig({
|
||||
modules: {
|
||||
syntax: true,
|
||||
toolbar: [
|
||||
['bold', 'italic', 'underline'],
|
||||
[{ header: [1, 2, 3, false] }],
|
||||
[{ list: 'ordered' }, { list: 'bullet' }],
|
||||
[{ color: [] }],
|
||||
['clean'],
|
||||
],
|
||||
},
|
||||
}) as any,
|
||||
DatePipe
|
||||
],
|
||||
templateUrl: './account.component.html',
|
||||
styleUrls: [
|
||||
@@ -77,9 +65,6 @@ export class AccountComponent {
|
||||
editorModules = TOOLBAR_OPTIONS;
|
||||
env = environment;
|
||||
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;
|
||||
validationMessages: ValidationMessage[] = [];
|
||||
customerTypeOptions: Array<{ value: string; label: string }> = [];
|
||||
|
||||
@@ -11,7 +11,6 @@ import { QuillModule } from 'ngx-quill';
|
||||
|
||||
import { NgSelectModule } from '@ng-select/ng-select';
|
||||
import { NgxCurrencyDirective } from 'ngx-currency';
|
||||
import { provideQuillConfig } from 'ngx-quill';
|
||||
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
import { AutoCompleteCompleteEvent, ImageProperty, createDefaultBusinessListing, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
|
||||
@@ -48,20 +47,6 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||
ValidatedTextareaComponent,
|
||||
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',
|
||||
styleUrls: [
|
||||
'./edit-business-listing.component.scss',
|
||||
|
||||
@@ -11,7 +11,7 @@ import { APP_ICONS } from '../../../utils/fontawesome-icons';
|
||||
import { NgSelectModule } from '@ng-select/ng-select';
|
||||
import { NgxCurrencyDirective } from 'ngx-currency';
|
||||
import { ImageCropperComponent } from 'ngx-image-cropper';
|
||||
import { QuillModule, provideQuillConfig } from 'ngx-quill';
|
||||
import { QuillModule } from 'ngx-quill';
|
||||
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
import { AutoCompleteCompleteEvent, ImageProperty, UploadParams, createDefaultCommercialPropertyListing, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
|
||||
@@ -53,20 +53,6 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||
ValidatedLocationComponent,
|
||||
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',
|
||||
styleUrls: [
|
||||
'./edit-commercial-property-listing.component.scss',
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
// SSR-safe: check if window exists (it doesn't on server-side)
|
||||
const hostname = typeof window !== 'undefined' ? window.location.hostname : 'localhost';
|
||||
// SSR-safe: check if window exists
|
||||
// Im Browser nehmen wir den aktuellen Host (z.B. 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 = {
|
||||
// apiBaseUrl: 'http://localhost:3000',
|
||||
apiBaseUrl: `http://${hostname}:4200`,
|
||||
// GETTER FUNCTION für die API URL (besser als statischer String für diesen Fall)
|
||||
apiBaseUrl: calculatedApiBaseUrl,
|
||||
imageBaseUrl: 'https://dev.bizmatch.net',
|
||||
buildVersion: '<BUILD_VERSION>',
|
||||
mailinfoUrl: 'https://dev.bizmatch.net',
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { environment_base } from './environment.base';
|
||||
|
||||
export const environment = environment_base;
|
||||
export const environment = { ...environment_base }; // Kopie erstellen
|
||||
|
||||
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.mailinfoUrl = 'https://www.bizmatch.net';
|
||||
environment.imageBaseUrl = 'https://api.bizmatch.net';
|
||||
environment.imageBaseUrl = 'https://www.bizmatch.net';// Ggf. auch auskommentieren, wenn Bilder lokal liegen
|
||||
|
||||
environment.POSTHOG_KEY = 'phc_eUIcIq0UPVzEDtZLy78klKhGudyagBz3goDlKx8SQFe';
|
||||
environment.POSTHOG_HOST = 'https://eu.i.posthog.com';
|
||||
@@ -66,39 +66,7 @@
|
||||
<!-- Note: Organization and WebSite schemas are now injected dynamically by SeoService -->
|
||||
<!-- with more complete data (telephone, foundingDate, knowsAbout, dual search actions) -->
|
||||
|
||||
<!-- LocalBusiness Schema for local visibility -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "LocalBusiness",
|
||||
"@id": "https://www.bizmatch.net/#localbusiness",
|
||||
"name": "BizMatch",
|
||||
"description": "Business brokerage and commercial real estate marketplace connecting buyers and sellers across the United States.",
|
||||
"url": "https://www.bizmatch.net",
|
||||
"logo": "https://www.bizmatch.net/assets/images/bizmatch-logo.png",
|
||||
"image": "https://www.bizmatch.net/assets/images/bizmatch-logo.png",
|
||||
"priceRange": "$$",
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
"streetAddress": "1001 Blucher Street",
|
||||
"addressLocality": "Corpus Christi",
|
||||
"addressRegion": "TX",
|
||||
"postalCode": "78401",
|
||||
"addressCountry": "US"
|
||||
},
|
||||
"geo": {
|
||||
"@type": "GeoCoordinates",
|
||||
"latitude": "27.7876",
|
||||
"longitude": "-97.3940"
|
||||
},
|
||||
"areaServed": {
|
||||
"@type": "Country",
|
||||
"name": "United States"
|
||||
},
|
||||
"serviceType": ["Business Brokerage", "Commercial Real Estate", "Business For Sale Listings"],
|
||||
"knowsAbout": ["Business Sales", "Commercial Properties", "Franchise Opportunities", "Business Valuation"]
|
||||
}
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="flex flex-col min-h-screen">
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// External CSS imports - these URL imports don't trigger deprecation warnings
|
||||
// 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://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
|
||||
// Note: These are loaded via angular.json styles array is the preferred approach,
|
||||
|
||||
62
docker-compose.yml
Normal file
62
docker-compose.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
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
|
||||
21
update.sh
Executable file
21
update.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/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."
|
||||
Reference in New Issue
Block a user