Final cleanup and documentation updates
This commit is contained in:
parent
1874d5f4ed
commit
897ab1ff77
|
|
@ -1,27 +1,27 @@
|
||||||
# Docker Code Sync Guide
|
# 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.
|
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 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 `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
|
### The Solution
|
||||||
You must restart or recreate the container to trigger a new build.
|
You must restart or recreate the container to trigger a new build.
|
||||||
|
|
||||||
**Option 1: Quick Restart (Recommended)**
|
**Option 1: Quick Restart (Recommended)**
|
||||||
Run this in the `bizmatch-server` directory:
|
Run this in the `bizmatch-server` directory:
|
||||||
```bash
|
```bash
|
||||||
docker-compose restart app
|
docker-compose restart app
|
||||||
```
|
```
|
||||||
|
|
||||||
**Option 2: Force Rebuild (If changes aren't picked up)**
|
**Option 2: Force Rebuild (If changes aren't picked up)**
|
||||||
If a simple restart doesn't work, use this to force a fresh build:
|
If a simple restart doesn't work, use this to force a fresh build:
|
||||||
```bash
|
```bash
|
||||||
docker-compose up -d --build app
|
docker-compose up -d --build app
|
||||||
```
|
```
|
||||||
|
|
||||||
### Summary for Other Laptops
|
### Summary for Other Laptops
|
||||||
1. **Pull** the latest changes from Git.
|
1. **Pull** the latest changes from Git.
|
||||||
2. **Execute** `docker-compose restart app`.
|
2. **Execute** `docker-compose restart app`.
|
||||||
3. **Verify** the logs for the new `WARN` debug messages.
|
3. **Verify** the logs for the new `WARN` debug messages.
|
||||||
.
|
.
|
||||||
146
FINAL_SUMMARY.md
146
FINAL_SUMMARY.md
|
|
@ -1,73 +1,73 @@
|
||||||
# Final Project Summary & Deployment Guide
|
# Final Project Summary & Deployment Guide
|
||||||
|
|
||||||
## Recent Changes (Last 3 Git Pushes)
|
## Recent Changes (Last 3 Git Pushes)
|
||||||
|
|
||||||
Here is a summary of the most recent activity on the repository:
|
Here is a summary of the most recent activity on the repository:
|
||||||
|
|
||||||
1. **`e3e726d`** - Timo, 3 minutes ago
|
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.`
|
* **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.
|
* **Impact**: Major initialization of the application structure, including core features and security baselines.
|
||||||
|
|
||||||
2. **`e32e43d`** - Timo, 10 hours ago
|
2. **`e32e43d`** - Timo, 10 hours ago
|
||||||
* **Message**: `docs: Add comprehensive deployment guide for BizMatch project.`
|
* **Message**: `docs: Add comprehensive deployment guide for BizMatch project.`
|
||||||
* **Impact**: Added documentation for deployment procedures.
|
* **Impact**: Added documentation for deployment procedures.
|
||||||
|
|
||||||
3. **`b52e47b`** - Timo, 10 hours ago
|
3. **`b52e47b`** - Timo, 10 hours ago
|
||||||
* **Message**: `feat: Initialize Angular SSR application with core pages, components, and server setup.`
|
* **Message**: `feat: Initialize Angular SSR application with core pages, components, and server setup.`
|
||||||
* **Impact**: Initial naming and setup of the Angular SSR environment.
|
* **Impact**: Initial naming and setup of the Angular SSR environment.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Deployment Instructions
|
## Deployment Instructions
|
||||||
|
|
||||||
### 1. Prerequisites
|
### 1. Prerequisites
|
||||||
* **Node.js**: Version **20.x** or higher is recommended.
|
* **Node.js**: Version **20.x** or higher is recommended.
|
||||||
* **Package Manager**: `npm`.
|
* **Package Manager**: `npm`.
|
||||||
|
|
||||||
### 2. Building for Production (SSR)
|
### 2. Building for Production (SSR)
|
||||||
The application is configured for **Angular SSR (Server-Side Rendering)**. You must build the application specifically for this mode.
|
The application is configured for **Angular SSR (Server-Side Rendering)**. You must build the application specifically for this mode.
|
||||||
|
|
||||||
**Steps:**
|
**Steps:**
|
||||||
1. Navigate to the project directory:
|
1. Navigate to the project directory:
|
||||||
```bash
|
```bash
|
||||||
cd bizmatch
|
cd bizmatch
|
||||||
```
|
```
|
||||||
2. Install dependencies:
|
2. Install dependencies:
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
3. Build the project:
|
3. Build the project:
|
||||||
```bash
|
```bash
|
||||||
npm run build:ssr
|
npm run build:ssr
|
||||||
```
|
```
|
||||||
* This command executes `node version.js` (to update build versions) and then `ng build --configuration prod`.
|
* 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`.
|
* Output will be generated in `dist/bizmatch/browser` and `dist/bizmatch/server`.
|
||||||
|
|
||||||
### 3. Running the Application
|
### 3. Running the Application
|
||||||
To start the production server:
|
To start the production server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run serve:ssr
|
npm run serve:ssr
|
||||||
```
|
```
|
||||||
* **Entry Point**: `dist/bizmatch/server/server.mjs`
|
* **Entry Point**: `dist/bizmatch/server/server.mjs`
|
||||||
* **Port**: The server listens on `process.env.PORT` or defaults to **4200**.
|
* **Port**: The server listens on `process.env.PORT` or defaults to **4200**.
|
||||||
|
|
||||||
### 4. Critical Deployment Checks (SSR & Polyfills)
|
### 4. Critical Deployment Checks (SSR & Polyfills)
|
||||||
**⚠️ IMPORTANT:**
|
**⚠️ 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.
|
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`
|
* **Polyfill Location**: `src/ssr-dom-polyfill.ts`
|
||||||
* **Server Verification**: Open `server.ts` and ensure the polyfill is imported **BEFORE** any other imports:
|
* **Server Verification**: Open `server.ts` and ensure the polyfill is imported **BEFORE** any other imports:
|
||||||
```typescript
|
```typescript
|
||||||
// IMPORTANT: DOM polyfill must be imported FIRST
|
// IMPORTANT: DOM polyfill must be imported FIRST
|
||||||
import './src/ssr-dom-polyfill';
|
import './src/ssr-dom-polyfill';
|
||||||
```
|
```
|
||||||
* **Why is this important?**
|
* **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.
|
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
|
### 5. Environment Variables & Security
|
||||||
* Ensure all necessary environment variables (e.g., Database URLs, API Keys) are configured in your deployment environment.
|
* 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.
|
* Since `server.ts` is an Express app, you can extend it to handle specialized headers or proxy configurations if needed.
|
||||||
|
|
||||||
### 6. Vulnerability Status
|
### 6. Vulnerability Status
|
||||||
* Please refer to `FINAL_VULNERABILITY_STATUS.md` for the most recent security audit and known issues.
|
* Please refer to `FINAL_VULNERABILITY_STATUS.md` for the most recent security audit and known issues.
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
FROM node:22-alpine
|
FROM node:22-alpine
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# GANZEN dist-Ordner kopieren, nicht nur bizmatch
|
# GANZEN dist-Ordner kopieren, nicht nur bizmatch
|
||||||
COPY dist ./dist
|
COPY dist ./dist
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
RUN npm ci --omit=dev
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
EXPOSE 4200
|
EXPOSE 4200
|
||||||
|
|
||||||
CMD ["node", "dist/bizmatch/server/server.mjs"]
|
CMD ["node", "dist/bizmatch/server/server.mjs"]
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
services:
|
services:
|
||||||
bizmatch-ssr:
|
bizmatch-ssr:
|
||||||
build: .
|
build: .
|
||||||
image: bizmatch-ssr
|
image: bizmatch-ssr
|
||||||
container_name: bizmatch-ssr
|
container_name: bizmatch-ssr
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- '4200:4200'
|
- '4200:4200'
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: DEVELOPMENT
|
NODE_ENV: DEVELOPMENT
|
||||||
|
|
|
||||||
|
|
@ -1,172 +1,172 @@
|
||||||
<div class="container mx-auto p-4">
|
<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">
|
<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>
|
<h1 class="text-2xl font-bold md:mb-4">My Listings</h1>
|
||||||
|
|
||||||
<!-- Desktop view -->
|
<!-- Desktop view -->
|
||||||
<div class="hidden md:block">
|
<div class="hidden md:block">
|
||||||
<table class="w-full table-fixed bg-white drop-shadow-inner-faint rounded-lg overflow-hidden">
|
<table class="w-full table-fixed bg-white drop-shadow-inner-faint rounded-lg overflow-hidden">
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col class="w-auto" />
|
<col class="w-auto" />
|
||||||
<!-- Title: restliche Breite -->
|
<!-- Title: restliche Breite -->
|
||||||
<col class="w-40" />
|
<col class="w-40" />
|
||||||
<!-- Category -->
|
<!-- Category -->
|
||||||
<col class="w-60" />
|
<col class="w-60" />
|
||||||
<!-- Located in -->
|
<!-- Located in -->
|
||||||
<col class="w-32" />
|
<col class="w-32" />
|
||||||
<!-- Price -->
|
<!-- Price -->
|
||||||
<col class="w-28" />
|
<col class="w-28" />
|
||||||
<!-- Internal # -->
|
<!-- Internal # -->
|
||||||
<col class="w-40" />
|
<col class="w-40" />
|
||||||
<!-- Publication Status -->
|
<!-- Publication Status -->
|
||||||
<col class="w-36" />
|
<col class="w-36" />
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<thead class="bg-gray-100">
|
<thead class="bg-gray-100">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<tr>
|
<tr>
|
||||||
<th class="py-2 px-4 text-left">Title</th>
|
<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">Category</th>
|
||||||
<th class="py-2 px-4 text-left">Located in</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">Price</th>
|
||||||
<th class="py-2 px-4 text-left">Internal #</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">Publication Status</th>
|
||||||
<th class="py-2 px-4 text-left whitespace-nowrap">Actions</th>
|
<th class="py-2 px-4 text-left whitespace-nowrap">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<!-- Filter row (zwischen Header und Inhalt) -->
|
<!-- Filter row (zwischen Header und Inhalt) -->
|
||||||
<tr class="bg-white border-b">
|
<tr class="bg-white border-b">
|
||||||
<th class="py-2 px-4">
|
<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()" />
|
<input type="text" class="w-full border rounded px-2 py-1" placeholder="Filter title…" [(ngModel)]="filters.title" (input)="applyFilters()" />
|
||||||
</th>
|
</th>
|
||||||
<!-- Category Filter -->
|
<!-- Category Filter -->
|
||||||
<th class="py-2 px-4">
|
<th class="py-2 px-4">
|
||||||
<select class="w-full border rounded px-2 py-1" [(ngModel)]="filters.category" (change)="applyFilters()">
|
<select class="w-full border rounded px-2 py-1" [(ngModel)]="filters.category" (change)="applyFilters()">
|
||||||
<option value="">All</option>
|
<option value="">All</option>
|
||||||
<option value="business">Business</option>
|
<option value="business">Business</option>
|
||||||
<option value="commercialProperty">Commercial Property</option>
|
<option value="commercialProperty">Commercial Property</option>
|
||||||
</select>
|
</select>
|
||||||
</th>
|
</th>
|
||||||
<th class="py-2 px-4">
|
<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()" />
|
<input type="text" class="w-full border rounded px-2 py-1" placeholder="City/County/State…" [(ngModel)]="filters.location" (input)="applyFilters()" />
|
||||||
</th>
|
</th>
|
||||||
<th class="py-2 px-4">
|
<th class="py-2 px-4">
|
||||||
<!-- Preis nicht gefiltert, daher leer -->
|
<!-- Preis nicht gefiltert, daher leer -->
|
||||||
</th>
|
</th>
|
||||||
<th class="py-2 px-4">
|
<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()" />
|
<input type="text" class="w-full border rounded px-2 py-1" placeholder="Internal #" [(ngModel)]="filters.internalListingNumber" (input)="applyFilters()" />
|
||||||
</th>
|
</th>
|
||||||
<th class="py-2 px-4">
|
<th class="py-2 px-4">
|
||||||
<select class="w-full border rounded px-2 py-1" [(ngModel)]="filters.status" (change)="applyFilters()">
|
<select class="w-full border rounded px-2 py-1" [(ngModel)]="filters.status" (change)="applyFilters()">
|
||||||
<option value="">All</option>
|
<option value="">All</option>
|
||||||
<option value="published">Published</option>
|
<option value="published">Published</option>
|
||||||
<option value="draft">Draft</option>
|
<option value="draft">Draft</option>
|
||||||
</select>
|
</select>
|
||||||
</th>
|
</th>
|
||||||
<th class="py-2 px-4">
|
<th class="py-2 px-4">
|
||||||
<button class="text-sm underline" (click)="clearFilters()">Clear</button>
|
<button class="text-sm underline" (click)="clearFilters()">Clear</button>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let listing of myListings" class="border-b">
|
<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.title }}</td>
|
||||||
<td class="py-2 px-4">{{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}</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.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">${{ listing.price ? listing.price.toLocaleString() : '' }}</td>
|
||||||
<td class="py-2 px-4 flex justify-center">
|
<td class="py-2 px-4 flex justify-center">
|
||||||
{{ listing.internalListingNumber ?? '—' }}
|
{{ listing.internalListingNumber ?? '—' }}
|
||||||
</td>
|
</td>
|
||||||
<td class="py-2 px-4">
|
<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">
|
<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' }}
|
{{ listing.draft ? 'Draft' : 'Published' }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="py-2 px-4 whitespace-nowrap">
|
<td class="py-2 px-4 whitespace-nowrap">
|
||||||
@if(listing.listingsCategory==='business'){
|
@if(listing.listingsCategory==='business'){
|
||||||
<button class="bg-green-500 text-white p-2 rounded-full mr-2" [routerLink]="['/editBusinessListing', listing.id]">
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
} @if(listing.listingsCategory==='commercialProperty'){
|
} @if(listing.listingsCategory==='commercialProperty'){
|
||||||
<button class="bg-green-500 text-white p-2 rounded-full mr-2" [routerLink]="['/editCommercialPropertyListing', listing.id]">
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
<button class="bg-orange-500 text-white p-2 rounded-full" (click)="confirm(listing)">
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
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"
|
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"
|
clip-rule="evenodd"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile view -->
|
<!-- Mobile view -->
|
||||||
<div class="md:hidden">
|
<div class="md:hidden">
|
||||||
<!-- Mobile Filter -->
|
<!-- Mobile Filter -->
|
||||||
<div class="bg-white drop-shadow-inner-faint rounded-lg p-4 mb-4 border">
|
<div class="bg-white drop-shadow-inner-faint rounded-lg p-4 mb-4 border">
|
||||||
<div class="grid grid-cols-1 gap-3">
|
<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="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="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()" />
|
<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()">
|
<select class="w-full border rounded px-3 py-2" [(ngModel)]="filters.status" (change)="applyFilters()">
|
||||||
<option value="">All</option>
|
<option value="">All</option>
|
||||||
<option value="published">Published</option>
|
<option value="published">Published</option>
|
||||||
<option value="draft">Draft</option>
|
<option value="draft">Draft</option>
|
||||||
</select>
|
</select>
|
||||||
<button class="text-sm underline justify-self-start" (click)="clearFilters()">Clear</button>
|
<button class="text-sm underline justify-self-start" (click)="clearFilters()">Clear</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngFor="let listing of myListings" class="bg-white drop-shadow-inner-faint rounded-lg p-4 mb-4">
|
<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>
|
<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">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">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">Price: ${{ listing.price.toLocaleString() }}</p>
|
||||||
<p class="text-gray-600 mb-2">Internal #: {{ listing.internalListingNumber ?? '—' }}</p>
|
<p class="text-gray-600 mb-2">Internal #: {{ listing.internalListingNumber ?? '—' }}</p>
|
||||||
<div class="flex items-center gap-2 mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<span class="text-gray-600">Publication Status:</span>
|
<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">
|
<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' }}
|
{{ listing.draft ? 'Draft' : 'Published' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-start">
|
<div class="flex justify-start">
|
||||||
@if(listing.listingsCategory==='business'){
|
@if(listing.listingsCategory==='business'){
|
||||||
<button class="bg-green-500 text-white p-2 rounded-full mr-2" [routerLink]="['/editBusinessListing', listing.id]">
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
} @if(listing.listingsCategory==='commercialProperty'){
|
} @if(listing.listingsCategory==='commercialProperty'){
|
||||||
<button class="bg-green-500 text-white p-2 rounded-full mr-2" [routerLink]="['/editCommercialPropertyListing', listing.id]">
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
<button class="bg-orange-500 text-white p-2 rounded-full" (click)="confirm(listing)">
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
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"
|
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"
|
clip-rule="evenodd"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-confirmation></app-confirmation>
|
<app-confirmation></app-confirmation>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { environment_base } from './environment.base';
|
import { environment_base } from './environment.base';
|
||||||
|
|
||||||
export const environment = environment_base;
|
export const environment = environment_base;
|
||||||
|
|
||||||
environment.apiBaseUrl = 'http://bizsearch.at-powan.ts.net:3001';
|
environment.apiBaseUrl = 'http://bizsearch.at-powan.ts.net:3001';
|
||||||
environment.mailinfoUrl = 'http://bizsearch.at-powan.ts.net';
|
environment.mailinfoUrl = 'http://bizsearch.at-powan.ts.net';
|
||||||
environment.imageBaseUrl = 'http://bizsearch.at-powan.ts.net';
|
environment.imageBaseUrl = 'http://bizsearch.at-powan.ts.net';
|
||||||
|
|
|
||||||
118
debug-inarray.ts
118
debug-inarray.ts
|
|
@ -1,59 +1,59 @@
|
||||||
|
|
||||||
import { and, inArray, sql, SQL } from 'drizzle-orm';
|
import { and, inArray, sql, SQL } from 'drizzle-orm';
|
||||||
import { businesses_json, users_json } from './bizmatch-server/src/drizzle/schema';
|
import { businesses_json, users_json } from './bizmatch-server/src/drizzle/schema';
|
||||||
|
|
||||||
// Mock criteria similar to what the user used
|
// Mock criteria similar to what the user used
|
||||||
const criteria: any = {
|
const criteria: any = {
|
||||||
types: ['retail'],
|
types: ['retail'],
|
||||||
brokerName: 'page',
|
brokerName: 'page',
|
||||||
criteriaType: 'businessListings'
|
criteriaType: 'businessListings'
|
||||||
};
|
};
|
||||||
|
|
||||||
const user = { role: 'guest', email: 'timo@example.com' };
|
const user = { role: 'guest', email: 'timo@example.com' };
|
||||||
|
|
||||||
function getWhereConditions(criteria: any, user: any): SQL[] {
|
function getWhereConditions(criteria: any, user: any): SQL[] {
|
||||||
const whereConditions: SQL[] = [];
|
const whereConditions: SQL[] = [];
|
||||||
|
|
||||||
// Category filter
|
// Category filter
|
||||||
if (criteria.types && criteria.types.length > 0) {
|
if (criteria.types && criteria.types.length > 0) {
|
||||||
// Suspected problematic line:
|
// Suspected problematic line:
|
||||||
whereConditions.push(inArray(sql`${businesses_json.data}->>'type'`, criteria.types));
|
whereConditions.push(inArray(sql`${businesses_json.data}->>'type'`, criteria.types));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broker filter
|
// Broker filter
|
||||||
if (criteria.brokerName) {
|
if (criteria.brokerName) {
|
||||||
const firstname = criteria.brokerName;
|
const firstname = criteria.brokerName;
|
||||||
const lastname = criteria.brokerName;
|
const lastname = criteria.brokerName;
|
||||||
whereConditions.push(
|
whereConditions.push(
|
||||||
sql`((${users_json.data}->>'firstname') ILIKE ${`%${firstname}%`} OR (${users_json.data}->>'lastname') ILIKE ${`%${lastname}%` bubble})`
|
sql`((${users_json.data}->>'firstname') ILIKE ${`%${firstname}%`} OR (${users_json.data}->>'lastname') ILIKE ${`%${lastname}%` bubble})`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draft check
|
// Draft check
|
||||||
if (user?.role !== 'admin') {
|
if (user?.role !== 'admin') {
|
||||||
whereConditions.push(
|
whereConditions.push(
|
||||||
sql`((${ businesses_json.email } = ${ user?.email || null}) OR(${ businesses_json.data } ->> 'draft')::boolean IS NOT TRUE)`
|
sql`((${ businesses_json.email } = ${ user?.email || null}) OR(${ businesses_json.data } ->> 'draft')::boolean IS NOT TRUE)`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return whereConditions;
|
return whereConditions;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conditions = getWhereConditions(criteria, user);
|
const conditions = getWhereConditions(criteria, user);
|
||||||
const combined = and(...conditions);
|
const combined = and(...conditions);
|
||||||
|
|
||||||
console.log('--- Conditions Count ---');
|
console.log('--- Conditions Count ---');
|
||||||
console.log(conditions.length);
|
console.log(conditions.length);
|
||||||
|
|
||||||
console.log('--- Generated SQL Fragment ---');
|
console.log('--- Generated SQL Fragment ---');
|
||||||
// We need a dummy query to see the full SQL
|
// 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
|
// Since we don't have a real DB connection here, we just inspect the SQL parts
|
||||||
// Drizzle conditions can be serialized to SQL strings
|
// Drizzle conditions can be serialized to SQL strings
|
||||||
// This is a simplified test
|
// This is a simplified test
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// In a real environment we would use a dummy pg adapter
|
// 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.');
|
console.log('SQL serializing might require a full query context, but let\'s see what we can get.');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue