Fix business filtering logic and add docker sync guide

This commit is contained in:
2026-01-12 13:58:45 +01:00
parent 4f8fd77f7d
commit adeefb199c
21 changed files with 538 additions and 69 deletions

View File

@@ -5,7 +5,7 @@ import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { ZodError } from 'zod';
import * as schema from '../drizzle/schema';
import { businesses_json, PG_CONNECTION, users_json } from '../drizzle/schema';
import { businesses_json, PG_CONNECTION } from '../drizzle/schema';
import { GeoService } from '../geo/geo.service';
import { BusinessListing, BusinessListingSchema } from '../models/db.model';
import { BusinessListingCriteria, JwtUser } from '../models/main.model';
@@ -18,27 +18,30 @@ export class BusinessListingService {
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
@Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,
private geoService?: GeoService,
) {}
) { }
private getWhereConditions(criteria: BusinessListingCriteria, user: JwtUser): SQL[] {
const whereConditions: SQL[] = [];
this.logger.info('getWhereConditions start', { criteria: JSON.stringify(criteria) });
if (criteria.city && criteria.searchType === 'exact') {
whereConditions.push(sql`(${businesses_json.data}->'location'->>'name') ILIKE ${criteria.city.name}`);
}
if (criteria.city && criteria.radius && criteria.searchType === 'radius' && criteria.radius) {
this.logger.debug('Adding radius search filter', { city: criteria.city.name, radius: criteria.radius });
const cityGeo = this.geoService.getCityWithCoords(criteria.state, criteria.city.name);
whereConditions.push(sql`${getDistanceQuery(businesses_json, cityGeo.latitude, cityGeo.longitude)} <= ${criteria.radius}`);
whereConditions.push(sql`(${getDistanceQuery(businesses_json, cityGeo.latitude, cityGeo.longitude)} <= ${criteria.radius})`);
}
if (criteria.types && Array.isArray(criteria.types) && criteria.types.length > 0) {
const validTypes = criteria.types.filter(t => t !== null && t !== undefined && t !== '');
if (validTypes.length > 0) {
whereConditions.push(inArray(sql`${businesses_json.data}->>'type'`, validTypes));
}
if (criteria.types && criteria.types.length > 0) {
this.logger.warn('Adding business category filter', { types: criteria.types });
// Use explicit SQL with IN for robust JSONB comparison
const typeValues = criteria.types.map(t => sql`${t}`);
whereConditions.push(sql`((${businesses_json.data}->>'type') IN (${sql.join(typeValues, sql`, `)}))`);
}
if (criteria.state) {
this.logger.debug('Adding state filter', { state: criteria.state });
whereConditions.push(sql`(${businesses_json.data}->'location'->>'state') = ${criteria.state}`);
}
@@ -105,27 +108,30 @@ export class BusinessListingService {
if (criteria.title && criteria.title.trim() !== '') {
const searchTerm = `%${criteria.title.trim()}%`;
whereConditions.push(
or(
sql`(${businesses_json.data}->>'title') ILIKE ${searchTerm}`,
sql`(${businesses_json.data}->>'description') ILIKE ${searchTerm}`
)
sql`((${businesses_json.data}->>'title') ILIKE ${searchTerm} OR (${businesses_json.data}->>'description') ILIKE ${searchTerm})`
);
}
if (criteria.brokerName) {
const { firstname, lastname } = splitName(criteria.brokerName);
if (firstname === lastname) {
whereConditions.push(sql`(${users_json.data}->>'firstname') ILIKE ${`%${firstname}%`} OR (${users_json.data}->>'lastname') ILIKE ${`%${lastname}%`}`);
whereConditions.push(
sql`((${schema.users_json.data}->>'firstname') ILIKE ${`%${firstname}%`} OR (${schema.users_json.data}->>'lastname') ILIKE ${`%${lastname}%`})`
);
} else {
whereConditions.push(sql`(${users_json.data}->>'firstname') ILIKE ${`%${firstname}%`} AND (${users_json.data}->>'lastname') ILIKE ${`%${lastname}%`}`);
whereConditions.push(
sql`((${schema.users_json.data}->>'firstname') ILIKE ${`%${firstname}%`} AND (${schema.users_json.data}->>'lastname') ILIKE ${`%${lastname}%`})`
);
}
}
if (criteria.email) {
whereConditions.push(eq(users_json.email, criteria.email));
whereConditions.push(eq(schema.users_json.email, criteria.email));
}
if (user?.role !== 'admin') {
whereConditions.push(or(eq(businesses_json.email, user?.email), sql`(${businesses_json.data}->>'draft')::boolean IS NOT TRUE`));
whereConditions.push(
sql`((${businesses_json.email} = ${user?.email || null}) OR (${businesses_json.data}->>'draft')::boolean IS NOT TRUE)`
);
}
whereConditions.push(and(sql`(${users_json.data}->>'customerType') = 'professional'`, sql`(${users_json.data}->>'customerSubType') = 'broker'`));
this.logger.warn('whereConditions count', { count: whereConditions.length });
return whereConditions;
}
@@ -135,24 +141,21 @@ export class BusinessListingService {
const query = this.conn
.select({
business: businesses_json,
brokerFirstName: sql`${users_json.data}->>'firstname'`.as('brokerFirstName'),
brokerLastName: sql`${users_json.data}->>'lastname'`.as('brokerLastName'),
brokerFirstName: sql`${schema.users_json.data}->>'firstname'`.as('brokerFirstName'),
brokerLastName: sql`${schema.users_json.data}->>'lastname'`.as('brokerLastName'),
})
.from(businesses_json)
.leftJoin(users_json, eq(businesses_json.email, users_json.email));
.leftJoin(schema.users_json, eq(businesses_json.email, schema.users_json.email));
const whereConditions = this.getWhereConditions(criteria, user);
// Uncomment for debugging filter issues:
// this.logger.info('Filter Criteria:', { criteria });
// this.logger.info('Where Conditions Count:', { count: whereConditions.length });
this.logger.warn('Filter Criteria:', { criteria: JSON.stringify(criteria) });
if (whereConditions.length > 0) {
const whereClause = and(...whereConditions);
query.where(whereClause);
const whereClause = sql.join(whereConditions, sql` AND `);
query.where(sql`(${whereClause})`);
// Uncomment for debugging SQL queries:
// this.logger.info('Generated SQL:', { sql: query.toSQL() });
this.logger.warn('Generated SQL:', { sql: query.toSQL().sql, params: query.toSQL().params });
}
// Sortierung
@@ -228,13 +231,13 @@ export class BusinessListingService {
}
async getBusinessListingsCount(criteria: BusinessListingCriteria, user: JwtUser): Promise<number> {
const countQuery = this.conn.select({ value: count() }).from(businesses_json).leftJoin(users_json, eq(businesses_json.email, users_json.email));
const countQuery = this.conn.select({ value: count() }).from(businesses_json).leftJoin(schema.users_json, eq(businesses_json.email, schema.users_json.email));
const whereConditions = this.getWhereConditions(criteria, user);
if (whereConditions.length > 0) {
const whereClause = and(...whereConditions);
countQuery.where(whereClause);
const whereClause = sql.join(whereConditions, sql` AND `);
countQuery.where(sql`(${whereClause})`);
}
const [{ value: totalCount }] = await countQuery;