Final cleanup and documentation updates

This commit is contained in:
knuthtimo-lab 2026-01-12 14:03:48 +01:00
parent 1874d5f4ed
commit 897ab1ff77
9 changed files with 360 additions and 360 deletions

View File

@ -1,27 +1,27 @@
# Docker Code Sync Guide
If you have made changes to the backend code and they don't seem to take effect (even though the files on disk are updated), it's because the Docker container is running from a pre-compiled `dist/` directory.
### The Problem
The `bizmatch-app` container compiles the TypeScript code *only once* when the container starts. It does not automatically watch for changes and recompile while running.
### The Solution
You must restart or recreate the container to trigger a new build.
**Option 1: Quick Restart (Recommended)**
Run this in the `bizmatch-server` directory:
```bash
docker-compose restart app
```
**Option 2: Force Rebuild (If changes aren't picked up)**
If a simple restart doesn't work, use this to force a fresh build:
```bash
docker-compose up -d --build app
```
### Summary for Other Laptops
1. **Pull** the latest changes from Git.
2. **Execute** `docker-compose restart app`.
3. **Verify** the logs for the new `WARN` debug messages.
# Docker Code Sync Guide
If you have made changes to the backend code and they don't seem to take effect (even though the files on disk are updated), it's because the Docker container is running from a pre-compiled `dist/` directory.
### The Problem
The `bizmatch-app` container compiles the TypeScript code *only once* when the container starts. It does not automatically watch for changes and recompile while running.
### The Solution
You must restart or recreate the container to trigger a new build.
**Option 1: Quick Restart (Recommended)**
Run this in the `bizmatch-server` directory:
```bash
docker-compose restart app
```
**Option 2: Force Rebuild (If changes aren't picked up)**
If a simple restart doesn't work, use this to force a fresh build:
```bash
docker-compose up -d --build app
```
### Summary for Other Laptops
1. **Pull** the latest changes from Git.
2. **Execute** `docker-compose restart app`.
3. **Verify** the logs for the new `WARN` debug messages.
.

View File

@ -1,73 +1,73 @@
# Final Project Summary & Deployment Guide
## Recent Changes (Last 3 Git Pushes)
Here is a summary of the most recent activity on the repository:
1. **`e3e726d`** - Timo, 3 minutes ago
* **Message**: `feat: Initialize BizMatch application with core UI components, routing, listing pages, backend services, migration scripts, and vulnerability management.`
* **Impact**: Major initialization of the application structure, including core features and security baselines.
2. **`e32e43d`** - Timo, 10 hours ago
* **Message**: `docs: Add comprehensive deployment guide for BizMatch project.`
* **Impact**: Added documentation for deployment procedures.
3. **`b52e47b`** - Timo, 10 hours ago
* **Message**: `feat: Initialize Angular SSR application with core pages, components, and server setup.`
* **Impact**: Initial naming and setup of the Angular SSR environment.
---
## Deployment Instructions
### 1. Prerequisites
* **Node.js**: Version **20.x** or higher is recommended.
* **Package Manager**: `npm`.
### 2. Building for Production (SSR)
The application is configured for **Angular SSR (Server-Side Rendering)**. You must build the application specifically for this mode.
**Steps:**
1. Navigate to the project directory:
```bash
cd bizmatch
```
2. Install dependencies:
```bash
npm install
```
3. Build the project:
```bash
npm run build:ssr
```
* This command executes `node version.js` (to update build versions) and then `ng build --configuration prod`.
* Output will be generated in `dist/bizmatch/browser` and `dist/bizmatch/server`.
### 3. Running the Application
To start the production server:
```bash
npm run serve:ssr
```
* **Entry Point**: `dist/bizmatch/server/server.mjs`
* **Port**: The server listens on `process.env.PORT` or defaults to **4200**.
### 4. Critical Deployment Checks (SSR & Polyfills)
**⚠️ IMPORTANT:**
The application uses a custom **DOM Polyfill** to support third-party libraries that might rely on browser-specific objects (like `window`, `document`) during server-side rendering.
* **Polyfill Location**: `src/ssr-dom-polyfill.ts`
* **Server Verification**: Open `server.ts` and ensure the polyfill is imported **BEFORE** any other imports:
```typescript
// IMPORTANT: DOM polyfill must be imported FIRST
import './src/ssr-dom-polyfill';
```
* **Why is this important?**
If this import is removed or moved down, you may encounter `ReferenceError: window is not defined` or `document is not defined` errors when the server tries to render pages containing Leaflet maps or other browser-only libraries.
### 5. Environment Variables & Security
* Ensure all necessary environment variables (e.g., Database URLs, API Keys) are configured in your deployment environment.
* Since `server.ts` is an Express app, you can extend it to handle specialized headers or proxy configurations if needed.
### 6. Vulnerability Status
* Please refer to `FINAL_VULNERABILITY_STATUS.md` for the most recent security audit and known issues.
# Final Project Summary & Deployment Guide
## Recent Changes (Last 3 Git Pushes)
Here is a summary of the most recent activity on the repository:
1. **`e3e726d`** - Timo, 3 minutes ago
* **Message**: `feat: Initialize BizMatch application with core UI components, routing, listing pages, backend services, migration scripts, and vulnerability management.`
* **Impact**: Major initialization of the application structure, including core features and security baselines.
2. **`e32e43d`** - Timo, 10 hours ago
* **Message**: `docs: Add comprehensive deployment guide for BizMatch project.`
* **Impact**: Added documentation for deployment procedures.
3. **`b52e47b`** - Timo, 10 hours ago
* **Message**: `feat: Initialize Angular SSR application with core pages, components, and server setup.`
* **Impact**: Initial naming and setup of the Angular SSR environment.
---
## Deployment Instructions
### 1. Prerequisites
* **Node.js**: Version **20.x** or higher is recommended.
* **Package Manager**: `npm`.
### 2. Building for Production (SSR)
The application is configured for **Angular SSR (Server-Side Rendering)**. You must build the application specifically for this mode.
**Steps:**
1. Navigate to the project directory:
```bash
cd bizmatch
```
2. Install dependencies:
```bash
npm install
```
3. Build the project:
```bash
npm run build:ssr
```
* This command executes `node version.js` (to update build versions) and then `ng build --configuration prod`.
* Output will be generated in `dist/bizmatch/browser` and `dist/bizmatch/server`.
### 3. Running the Application
To start the production server:
```bash
npm run serve:ssr
```
* **Entry Point**: `dist/bizmatch/server/server.mjs`
* **Port**: The server listens on `process.env.PORT` or defaults to **4200**.
### 4. Critical Deployment Checks (SSR & Polyfills)
**⚠️ IMPORTANT:**
The application uses a custom **DOM Polyfill** to support third-party libraries that might rely on browser-specific objects (like `window`, `document`) during server-side rendering.
* **Polyfill Location**: `src/ssr-dom-polyfill.ts`
* **Server Verification**: Open `server.ts` and ensure the polyfill is imported **BEFORE** any other imports:
```typescript
// IMPORTANT: DOM polyfill must be imported FIRST
import './src/ssr-dom-polyfill';
```
* **Why is this important?**
If this import is removed or moved down, you may encounter `ReferenceError: window is not defined` or `document is not defined` errors when the server tries to render pages containing Leaflet maps or other browser-only libraries.
### 5. Environment Variables & Security
* Ensure all necessary environment variables (e.g., Database URLs, API Keys) are configured in your deployment environment.
* Since `server.ts` is an Express app, you can extend it to handle specialized headers or proxy configurations if needed.
### 6. Vulnerability Status
* Please refer to `FINAL_VULNERABILITY_STATUS.md` for the most recent security audit and known issues.

0
bizmatch-server/prod.dump Normal file → Executable file
View File

View File

@ -1,13 +1,13 @@
FROM node:22-alpine
WORKDIR /app
# GANZEN dist-Ordner kopieren, nicht nur bizmatch
COPY dist ./dist
COPY package*.json ./
RUN npm ci --omit=dev
EXPOSE 4200
CMD ["node", "dist/bizmatch/server/server.mjs"]
FROM node:22-alpine
WORKDIR /app
# GANZEN dist-Ordner kopieren, nicht nur bizmatch
COPY dist ./dist
COPY package*.json ./
RUN npm ci --omit=dev
EXPOSE 4200
CMD ["node", "dist/bizmatch/server/server.mjs"]

View File

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

View File

@ -1,172 +1,172 @@
<div class="container mx-auto p-4">
<div class="bg-white rounded-lg drop-shadow-custom-bg-mobile md:drop-shadow-custom-bg p-6">
<h1 class="text-2xl font-bold md:mb-4">My Listings</h1>
<!-- Desktop view -->
<div class="hidden md:block">
<table class="w-full table-fixed bg-white drop-shadow-inner-faint rounded-lg overflow-hidden">
<colgroup>
<col class="w-auto" />
<!-- Title: restliche Breite -->
<col class="w-40" />
<!-- Category -->
<col class="w-60" />
<!-- Located in -->
<col class="w-32" />
<!-- Price -->
<col class="w-28" />
<!-- Internal # -->
<col class="w-40" />
<!-- Publication Status -->
<col class="w-36" />
<!-- Actions -->
</colgroup>
<thead class="bg-gray-100">
<!-- Header -->
<tr>
<th class="py-2 px-4 text-left">Title</th>
<th class="py-2 px-4 text-left">Category</th>
<th class="py-2 px-4 text-left">Located in</th>
<th class="py-2 px-4 text-left">Price</th>
<th class="py-2 px-4 text-left">Internal #</th>
<th class="py-2 px-4 text-left">Publication Status</th>
<th class="py-2 px-4 text-left whitespace-nowrap">Actions</th>
</tr>
<!-- Filter row (zwischen Header und Inhalt) -->
<tr class="bg-white border-b">
<th class="py-2 px-4">
<input type="text" class="w-full border rounded px-2 py-1" placeholder="Filter title…" [(ngModel)]="filters.title" (input)="applyFilters()" />
</th>
<!-- Category Filter -->
<th class="py-2 px-4">
<select class="w-full border rounded px-2 py-1" [(ngModel)]="filters.category" (change)="applyFilters()">
<option value="">All</option>
<option value="business">Business</option>
<option value="commercialProperty">Commercial Property</option>
</select>
</th>
<th class="py-2 px-4">
<input type="text" class="w-full border rounded px-2 py-1" placeholder="City/County/State…" [(ngModel)]="filters.location" (input)="applyFilters()" />
</th>
<th class="py-2 px-4">
<!-- Preis nicht gefiltert, daher leer -->
</th>
<th class="py-2 px-4">
<input type="text" class="w-full border rounded px-2 py-1" placeholder="Internal #" [(ngModel)]="filters.internalListingNumber" (input)="applyFilters()" />
</th>
<th class="py-2 px-4">
<select class="w-full border rounded px-2 py-1" [(ngModel)]="filters.status" (change)="applyFilters()">
<option value="">All</option>
<option value="published">Published</option>
<option value="draft">Draft</option>
</select>
</th>
<th class="py-2 px-4">
<button class="text-sm underline" (click)="clearFilters()">Clear</button>
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let listing of myListings" class="border-b">
<td class="py-2 px-4">{{ listing.title }}</td>
<td class="py-2 px-4">{{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}</td>
<td class="py-2 px-4">{{ listing.location.name ? listing.location.name : listing.location.county ? listing.location.county : this.selectOptions.getState(listing.location.state) }}</td>
<td class="py-2 px-4">${{ listing.price ? listing.price.toLocaleString() : '' }}</td>
<td class="py-2 px-4 flex justify-center">
{{ listing.internalListingNumber ?? '—' }}
</td>
<td class="py-2 px-4">
<span class="{{ listing.draft ? 'bg-yellow-100 text-yellow-800' : 'bg-green-100 text-green-800' }} py-1 px-2 rounded-full text-xs font-medium">
{{ listing.draft ? 'Draft' : 'Published' }}
</span>
</td>
<td class="py-2 px-4 whitespace-nowrap">
@if(listing.listingsCategory==='business'){
<button class="bg-green-500 text-white p-2 rounded-full mr-2" [routerLink]="['/editBusinessListing', listing.id]">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>
</button>
} @if(listing.listingsCategory==='commercialProperty'){
<button class="bg-green-500 text-white p-2 rounded-full mr-2" [routerLink]="['/editCommercialPropertyListing', listing.id]">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>
</button>
}
<button class="bg-orange-500 text-white p-2 rounded-full" (click)="confirm(listing)">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fill-rule="evenodd"
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
clip-rule="evenodd"
/>
</svg>
</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Mobile view -->
<div class="md:hidden">
<!-- Mobile Filter -->
<div class="bg-white drop-shadow-inner-faint rounded-lg p-4 mb-4 border">
<div class="grid grid-cols-1 gap-3">
<input type="text" class="w-full border rounded px-3 py-2" placeholder="Filter title…" [(ngModel)]="filters.title" (input)="applyFilters()" />
<input type="text" class="w-full border rounded px-3 py-2" placeholder="City/County/State…" [(ngModel)]="filters.location" (input)="applyFilters()" />
<input type="text" class="w-full border rounded px-3 py-2" placeholder="Internal #" [(ngModel)]="filters.internalListingNumber" (input)="applyFilters()" />
<select class="w-full border rounded px-3 py-2" [(ngModel)]="filters.status" (change)="applyFilters()">
<option value="">All</option>
<option value="published">Published</option>
<option value="draft">Draft</option>
</select>
<button class="text-sm underline justify-self-start" (click)="clearFilters()">Clear</button>
</div>
</div>
<div *ngFor="let listing of myListings" class="bg-white drop-shadow-inner-faint rounded-lg p-4 mb-4">
<h2 class="text-xl font-semibold mb-2">{{ listing.title }}</h2>
<p class="text-gray-600 mb-2">Category: {{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}</p>
<p class="text-gray-600 mb-2">Located in: {{ listing.location?.name ? listing.location.name : listing.location?.county }} - {{ listing.location?.state }}</p>
<p class="text-gray-600 mb-2">Price: ${{ listing.price.toLocaleString() }}</p>
<p class="text-gray-600 mb-2">Internal #: {{ listing.internalListingNumber ?? '—' }}</p>
<div class="flex items-center gap-2 mb-2">
<span class="text-gray-600">Publication Status:</span>
<span class="{{ listing.draft ? 'bg-yellow-100 text-yellow-800' : 'bg-green-100 text-green-800' }} py-1 px-2 rounded-full text-xs font-medium">
{{ listing.draft ? 'Draft' : 'Published' }}
</span>
</div>
<div class="flex justify-start">
@if(listing.listingsCategory==='business'){
<button class="bg-green-500 text-white p-2 rounded-full mr-2" [routerLink]="['/editBusinessListing', listing.id]">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>
</button>
} @if(listing.listingsCategory==='commercialProperty'){
<button class="bg-green-500 text-white p-2 rounded-full mr-2" [routerLink]="['/editCommercialPropertyListing', listing.id]">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>
</button>
}
<button class="bg-orange-500 text-white p-2 rounded-full" (click)="confirm(listing)">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fill-rule="evenodd"
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
clip-rule="evenodd"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<app-confirmation></app-confirmation>
<div class="container mx-auto p-4">
<div class="bg-white rounded-lg drop-shadow-custom-bg-mobile md:drop-shadow-custom-bg p-6">
<h1 class="text-2xl font-bold md:mb-4">My Listings</h1>
<!-- Desktop view -->
<div class="hidden md:block">
<table class="w-full table-fixed bg-white drop-shadow-inner-faint rounded-lg overflow-hidden">
<colgroup>
<col class="w-auto" />
<!-- Title: restliche Breite -->
<col class="w-40" />
<!-- Category -->
<col class="w-60" />
<!-- Located in -->
<col class="w-32" />
<!-- Price -->
<col class="w-28" />
<!-- Internal # -->
<col class="w-40" />
<!-- Publication Status -->
<col class="w-36" />
<!-- Actions -->
</colgroup>
<thead class="bg-gray-100">
<!-- Header -->
<tr>
<th class="py-2 px-4 text-left">Title</th>
<th class="py-2 px-4 text-left">Category</th>
<th class="py-2 px-4 text-left">Located in</th>
<th class="py-2 px-4 text-left">Price</th>
<th class="py-2 px-4 text-left">Internal #</th>
<th class="py-2 px-4 text-left">Publication Status</th>
<th class="py-2 px-4 text-left whitespace-nowrap">Actions</th>
</tr>
<!-- Filter row (zwischen Header und Inhalt) -->
<tr class="bg-white border-b">
<th class="py-2 px-4">
<input type="text" class="w-full border rounded px-2 py-1" placeholder="Filter title…" [(ngModel)]="filters.title" (input)="applyFilters()" />
</th>
<!-- Category Filter -->
<th class="py-2 px-4">
<select class="w-full border rounded px-2 py-1" [(ngModel)]="filters.category" (change)="applyFilters()">
<option value="">All</option>
<option value="business">Business</option>
<option value="commercialProperty">Commercial Property</option>
</select>
</th>
<th class="py-2 px-4">
<input type="text" class="w-full border rounded px-2 py-1" placeholder="City/County/State…" [(ngModel)]="filters.location" (input)="applyFilters()" />
</th>
<th class="py-2 px-4">
<!-- Preis nicht gefiltert, daher leer -->
</th>
<th class="py-2 px-4">
<input type="text" class="w-full border rounded px-2 py-1" placeholder="Internal #" [(ngModel)]="filters.internalListingNumber" (input)="applyFilters()" />
</th>
<th class="py-2 px-4">
<select class="w-full border rounded px-2 py-1" [(ngModel)]="filters.status" (change)="applyFilters()">
<option value="">All</option>
<option value="published">Published</option>
<option value="draft">Draft</option>
</select>
</th>
<th class="py-2 px-4">
<button class="text-sm underline" (click)="clearFilters()">Clear</button>
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let listing of myListings" class="border-b">
<td class="py-2 px-4">{{ listing.title }}</td>
<td class="py-2 px-4">{{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}</td>
<td class="py-2 px-4">{{ listing.location.name ? listing.location.name : listing.location.county ? listing.location.county : this.selectOptions.getState(listing.location.state) }}</td>
<td class="py-2 px-4">${{ listing.price ? listing.price.toLocaleString() : '' }}</td>
<td class="py-2 px-4 flex justify-center">
{{ listing.internalListingNumber ?? '—' }}
</td>
<td class="py-2 px-4">
<span class="{{ listing.draft ? 'bg-yellow-100 text-yellow-800' : 'bg-green-100 text-green-800' }} py-1 px-2 rounded-full text-xs font-medium">
{{ listing.draft ? 'Draft' : 'Published' }}
</span>
</td>
<td class="py-2 px-4 whitespace-nowrap">
@if(listing.listingsCategory==='business'){
<button class="bg-green-500 text-white p-2 rounded-full mr-2" [routerLink]="['/editBusinessListing', listing.id]">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>
</button>
} @if(listing.listingsCategory==='commercialProperty'){
<button class="bg-green-500 text-white p-2 rounded-full mr-2" [routerLink]="['/editCommercialPropertyListing', listing.id]">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>
</button>
}
<button class="bg-orange-500 text-white p-2 rounded-full" (click)="confirm(listing)">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fill-rule="evenodd"
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
clip-rule="evenodd"
/>
</svg>
</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Mobile view -->
<div class="md:hidden">
<!-- Mobile Filter -->
<div class="bg-white drop-shadow-inner-faint rounded-lg p-4 mb-4 border">
<div class="grid grid-cols-1 gap-3">
<input type="text" class="w-full border rounded px-3 py-2" placeholder="Filter title…" [(ngModel)]="filters.title" (input)="applyFilters()" />
<input type="text" class="w-full border rounded px-3 py-2" placeholder="City/County/State…" [(ngModel)]="filters.location" (input)="applyFilters()" />
<input type="text" class="w-full border rounded px-3 py-2" placeholder="Internal #" [(ngModel)]="filters.internalListingNumber" (input)="applyFilters()" />
<select class="w-full border rounded px-3 py-2" [(ngModel)]="filters.status" (change)="applyFilters()">
<option value="">All</option>
<option value="published">Published</option>
<option value="draft">Draft</option>
</select>
<button class="text-sm underline justify-self-start" (click)="clearFilters()">Clear</button>
</div>
</div>
<div *ngFor="let listing of myListings" class="bg-white drop-shadow-inner-faint rounded-lg p-4 mb-4">
<h2 class="text-xl font-semibold mb-2">{{ listing.title }}</h2>
<p class="text-gray-600 mb-2">Category: {{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}</p>
<p class="text-gray-600 mb-2">Located in: {{ listing.location?.name ? listing.location.name : listing.location?.county }} - {{ listing.location?.state }}</p>
<p class="text-gray-600 mb-2">Price: ${{ listing.price.toLocaleString() }}</p>
<p class="text-gray-600 mb-2">Internal #: {{ listing.internalListingNumber ?? '—' }}</p>
<div class="flex items-center gap-2 mb-2">
<span class="text-gray-600">Publication Status:</span>
<span class="{{ listing.draft ? 'bg-yellow-100 text-yellow-800' : 'bg-green-100 text-green-800' }} py-1 px-2 rounded-full text-xs font-medium">
{{ listing.draft ? 'Draft' : 'Published' }}
</span>
</div>
<div class="flex justify-start">
@if(listing.listingsCategory==='business'){
<button class="bg-green-500 text-white p-2 rounded-full mr-2" [routerLink]="['/editBusinessListing', listing.id]">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>
</button>
} @if(listing.listingsCategory==='commercialProperty'){
<button class="bg-green-500 text-white p-2 rounded-full mr-2" [routerLink]="['/editCommercialPropertyListing', listing.id]">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>
</button>
}
<button class="bg-orange-500 text-white p-2 rounded-full" (click)="confirm(listing)">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fill-rule="evenodd"
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
clip-rule="evenodd"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<app-confirmation></app-confirmation>

View File

@ -1,7 +1,7 @@
import { environment_base } from './environment.base';
export const environment = environment_base;
environment.apiBaseUrl = 'http://bizsearch.at-powan.ts.net:3001';
environment.mailinfoUrl = 'http://bizsearch.at-powan.ts.net';
environment.imageBaseUrl = 'http://bizsearch.at-powan.ts.net';
import { environment_base } from './environment.base';
export const environment = environment_base;
environment.apiBaseUrl = 'http://bizsearch.at-powan.ts.net:3001';
environment.mailinfoUrl = 'http://bizsearch.at-powan.ts.net';
environment.imageBaseUrl = 'http://bizsearch.at-powan.ts.net';

View File

@ -1,59 +1,59 @@
import { and, inArray, sql, SQL } from 'drizzle-orm';
import { businesses_json, users_json } from './bizmatch-server/src/drizzle/schema';
// Mock criteria similar to what the user used
const criteria: any = {
types: ['retail'],
brokerName: 'page',
criteriaType: 'businessListings'
};
const user = { role: 'guest', email: 'timo@example.com' };
function getWhereConditions(criteria: any, user: any): SQL[] {
const whereConditions: SQL[] = [];
// Category filter
if (criteria.types && criteria.types.length > 0) {
// Suspected problematic line:
whereConditions.push(inArray(sql`${businesses_json.data}->>'type'`, criteria.types));
}
// Broker filter
if (criteria.brokerName) {
const firstname = criteria.brokerName;
const lastname = criteria.brokerName;
whereConditions.push(
sql`((${users_json.data}->>'firstname') ILIKE ${`%${firstname}%`} OR (${users_json.data}->>'lastname') ILIKE ${`%${lastname}%` bubble})`
);
}
// Draft check
if (user?.role !== 'admin') {
whereConditions.push(
sql`((${ businesses_json.email } = ${ user?.email || null}) OR(${ businesses_json.data } ->> 'draft')::boolean IS NOT TRUE)`
);
}
return whereConditions;
}
const conditions = getWhereConditions(criteria, user);
const combined = and(...conditions);
console.log('--- Conditions Count ---');
console.log(conditions.length);
console.log('--- Generated SQL Fragment ---');
// We need a dummy query to see the full SQL
// Since we don't have a real DB connection here, we just inspect the SQL parts
// Drizzle conditions can be serialized to SQL strings
// This is a simplified test
try {
// In a real environment we would use a dummy pg adapter
console.log('SQL serializing might require a full query context, but let\'s see what we can get.');
} catch (e) {
console.error(e);
}
import { and, inArray, sql, SQL } from 'drizzle-orm';
import { businesses_json, users_json } from './bizmatch-server/src/drizzle/schema';
// Mock criteria similar to what the user used
const criteria: any = {
types: ['retail'],
brokerName: 'page',
criteriaType: 'businessListings'
};
const user = { role: 'guest', email: 'timo@example.com' };
function getWhereConditions(criteria: any, user: any): SQL[] {
const whereConditions: SQL[] = [];
// Category filter
if (criteria.types && criteria.types.length > 0) {
// Suspected problematic line:
whereConditions.push(inArray(sql`${businesses_json.data}->>'type'`, criteria.types));
}
// Broker filter
if (criteria.brokerName) {
const firstname = criteria.brokerName;
const lastname = criteria.brokerName;
whereConditions.push(
sql`((${users_json.data}->>'firstname') ILIKE ${`%${firstname}%`} OR (${users_json.data}->>'lastname') ILIKE ${`%${lastname}%` bubble})`
);
}
// Draft check
if (user?.role !== 'admin') {
whereConditions.push(
sql`((${ businesses_json.email } = ${ user?.email || null}) OR(${ businesses_json.data } ->> 'draft')::boolean IS NOT TRUE)`
);
}
return whereConditions;
}
const conditions = getWhereConditions(criteria, user);
const combined = and(...conditions);
console.log('--- Conditions Count ---');
console.log(conditions.length);
console.log('--- Generated SQL Fragment ---');
// We need a dummy query to see the full SQL
// Since we don't have a real DB connection here, we just inspect the SQL parts
// Drizzle conditions can be serialized to SQL strings
// This is a simplified test
try {
// In a real environment we would use a dummy pg adapter
console.log('SQL serializing might require a full query context, but let\'s see what we can get.');
} catch (e) {
console.error(e);
}

0
fix-vulnerabilities.sh Normal file → Executable file
View File