location radius search
This commit is contained in:
@@ -96,8 +96,8 @@ for (let index = 0; index < usersData.length; index++) {
|
||||
user.companyLocation = userData.companyLocation;
|
||||
const [city, state] = user.companyLocation.split('-').map(e => e.trim());
|
||||
const cityGeo = geos.states.find(s => s.state_code === state).cities.find(c => c.name === city);
|
||||
const latitude = cityGeo.latitude;
|
||||
const longitude = cityGeo.longitude;
|
||||
user.latitude = cityGeo.latitude;
|
||||
user.longitude = cityGeo.longitude;
|
||||
user.offeredServices = userData.offeredServices;
|
||||
user.gender = userData.gender;
|
||||
user.customerType = 'professional';
|
||||
@@ -151,8 +151,8 @@ for (let index = 0; index < commercialJsonData.length; index++) {
|
||||
commercial.type = sso.typesOfCommercialProperty.find(e => e.oldValue === String(commercial.type)).value;
|
||||
const cityGeo = geos.states.find(s => s.state_code === commercial.state).cities.find(c => c.name === commercial.city);
|
||||
try {
|
||||
const latitude = cityGeo.latitude;
|
||||
const longitude = cityGeo.longitude;
|
||||
commercial.latitude = cityGeo.latitude;
|
||||
commercial.longitude = cityGeo.longitude;
|
||||
} catch (e) {
|
||||
console.log(`----------------> ERROR ${commercial.state} - ${commercial.city}`);
|
||||
}
|
||||
@@ -191,8 +191,8 @@ for (let index = 0; index < businessJsonData.length; index++) {
|
||||
business.imageName = emailToDirName(user.email);
|
||||
const cityGeo = geos.states.find(s => s.state_code === business.state).cities.find(c => c.name === business.city);
|
||||
try {
|
||||
const latitude = cityGeo.latitude;
|
||||
const longitude = cityGeo.longitude;
|
||||
business.latitude = cityGeo.latitude;
|
||||
business.longitude = cityGeo.longitude;
|
||||
} catch (e) {
|
||||
console.log(`----------------> ERROR ${business.state} - ${business.city}`);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,9 @@ export class GeoService {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return state ? result.filter(e => e.state_code.toLowerCase() === state.toLowerCase()) : result;
|
||||
}
|
||||
getCityWithCoords(state: string, city: string): City {
|
||||
return this.geo.states.find(s => s.state_code === state).cities.find(c => c.name === city);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,10 @@ import { Logger } from 'winston';
|
||||
import * as schema from '../drizzle/schema.js';
|
||||
import { businesses, PG_CONNECTION } from '../drizzle/schema.js';
|
||||
import { FileService } from '../file/file.service.js';
|
||||
import { GeoService } from '../geo/geo.service.js';
|
||||
import { BusinessListing, CommercialPropertyListing } from '../models/db.model';
|
||||
import { BusinessListingCriteria, emailToDirName, JwtUser } from '../models/main.model.js';
|
||||
|
||||
const EARTH_RADIUS_KM = 6371; // Erdradius in Kilometern
|
||||
|
||||
const getDistanceQuery = (lat: number, lon: number) => sql`
|
||||
${EARTH_RADIUS_KM} * 2 * ASIN(SQRT(
|
||||
POWER(SIN((${lat} - ${businesses.latitude}) * PI() / 180 / 2), 2) +
|
||||
COS(${lat} * PI() / 180) * COS(${businesses.latitude} * PI() / 180) *
|
||||
POWER(SIN((${lon} - ${businesses.longitude}) * PI() / 180 / 2), 2)
|
||||
))
|
||||
`;
|
||||
import { getDistanceQuery } from '../utils.js';
|
||||
|
||||
@Injectable()
|
||||
export class BusinessListingService {
|
||||
@@ -25,15 +17,19 @@ export class BusinessListingService {
|
||||
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
||||
@Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,
|
||||
private fileService: FileService,
|
||||
private geoService: GeoService,
|
||||
) {}
|
||||
|
||||
private getWhereConditions(criteria: BusinessListingCriteria): SQL[] {
|
||||
const whereConditions: SQL[] = [];
|
||||
|
||||
if (criteria.city) {
|
||||
if (criteria.city && criteria.searchType === 'exact') {
|
||||
whereConditions.push(ilike(businesses.city, `%${criteria.city}%`));
|
||||
}
|
||||
|
||||
if (criteria.city && criteria.radius && criteria.searchType === 'radius' && criteria.radius) {
|
||||
const cityGeo = this.geoService.getCityWithCoords(criteria.state, criteria.city);
|
||||
whereConditions.push(sql`${getDistanceQuery(businesses, parseFloat(cityGeo.latitude), parseFloat(cityGeo.longitude))} <= ${criteria.radius}`);
|
||||
}
|
||||
if (criteria.types && criteria.types.length > 0) {
|
||||
whereConditions.push(inArray(businesses.type, criteria.types));
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ import { Logger } from 'winston';
|
||||
import * as schema from '../drizzle/schema.js';
|
||||
import { commercials, PG_CONNECTION } from '../drizzle/schema.js';
|
||||
import { FileService } from '../file/file.service.js';
|
||||
import { GeoService } from '../geo/geo.service.js';
|
||||
import { CommercialPropertyListing } from '../models/db.model';
|
||||
import { CommercialPropertyListingCriteria, emailToDirName, JwtUser } from '../models/main.model.js';
|
||||
import { getDistanceQuery } from '../utils.js';
|
||||
|
||||
@Injectable()
|
||||
export class CommercialPropertyService {
|
||||
@@ -15,14 +17,18 @@ export class CommercialPropertyService {
|
||||
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
||||
@Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,
|
||||
private fileService: FileService,
|
||||
private geoService: GeoService,
|
||||
) {}
|
||||
private getWhereConditions(criteria: CommercialPropertyListingCriteria): SQL[] {
|
||||
const whereConditions: SQL[] = [];
|
||||
|
||||
if (criteria.city) {
|
||||
if (criteria.city && criteria.searchType === 'exact') {
|
||||
whereConditions.push(ilike(schema.commercials.city, `%${criteria.city}%`));
|
||||
}
|
||||
|
||||
if (criteria.city && criteria.radius && criteria.searchType === 'radius' && criteria.radius) {
|
||||
const cityGeo = this.geoService.getCityWithCoords(criteria.state, criteria.city);
|
||||
whereConditions.push(sql`${getDistanceQuery(commercials, parseFloat(cityGeo.latitude), parseFloat(cityGeo.longitude))} <= ${criteria.radius}`);
|
||||
}
|
||||
if (criteria.types && criteria.types.length > 0) {
|
||||
whereConditions.push(inArray(schema.commercials.type, criteria.types));
|
||||
}
|
||||
|
||||
@@ -7,14 +7,16 @@ import { BrokerListingsController } from './broker-listings.controller.js';
|
||||
import { BusinessListingsController } from './business-listings.controller.js';
|
||||
import { CommercialPropertyListingsController } from './commercial-property-listings.controller.js';
|
||||
|
||||
import { GeoModule } from '../geo/geo.module.js';
|
||||
import { GeoService } from '../geo/geo.service.js';
|
||||
import { BusinessListingService } from './business-listing.service.js';
|
||||
import { CommercialPropertyService } from './commercial-property.service.js';
|
||||
import { UnknownListingsController } from './unknown-listings.controller.js';
|
||||
|
||||
@Module({
|
||||
imports: [DrizzleModule, AuthModule],
|
||||
imports: [DrizzleModule, AuthModule, GeoModule],
|
||||
controllers: [BusinessListingsController, CommercialPropertyListingsController, UnknownListingsController, BrokerListingsController],
|
||||
providers: [BusinessListingService, CommercialPropertyService, FileService, UserService, BusinessListingService, CommercialPropertyService],
|
||||
providers: [BusinessListingService, CommercialPropertyService, FileService, UserService, BusinessListingService, CommercialPropertyService, GeoService],
|
||||
exports: [BusinessListingService, CommercialPropertyService],
|
||||
})
|
||||
export class ListingsModule {}
|
||||
|
||||
@@ -5,6 +5,8 @@ import path, { join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { DrizzleModule } from '../drizzle/drizzle.module.js';
|
||||
import { FileService } from '../file/file.service.js';
|
||||
import { GeoModule } from '../geo/geo.module.js';
|
||||
import { GeoService } from '../geo/geo.service.js';
|
||||
import { UserModule } from '../user/user.module.js';
|
||||
import { UserService } from '../user/user.service.js';
|
||||
import { MailController } from './mail.controller.js';
|
||||
@@ -17,6 +19,7 @@ const password = process.env.amazon_password;
|
||||
imports: [
|
||||
DrizzleModule,
|
||||
UserModule,
|
||||
GeoModule,
|
||||
MailerModule.forRoot({
|
||||
transport: {
|
||||
host: 'email-smtp.us-east-2.amazonaws.com',
|
||||
@@ -39,7 +42,7 @@ const password = process.env.amazon_password;
|
||||
},
|
||||
}),
|
||||
],
|
||||
providers: [MailService, UserService, FileService],
|
||||
providers: [MailService, UserService, FileService, GeoService],
|
||||
controllers: [MailController],
|
||||
})
|
||||
export class MailModule {}
|
||||
|
||||
@@ -58,12 +58,14 @@ export interface ListCriteria {
|
||||
length: number;
|
||||
page: number;
|
||||
types: string[];
|
||||
state: string;
|
||||
city: string;
|
||||
prompt: string;
|
||||
searchType: 'exact' | 'radius';
|
||||
radius: number;
|
||||
criteriaType: 'business' | 'commercialProperty' | 'broker';
|
||||
}
|
||||
export interface BusinessListingCriteria extends ListCriteria {
|
||||
state: string;
|
||||
county: string;
|
||||
minPrice: number;
|
||||
maxPrice: number;
|
||||
@@ -83,7 +85,6 @@ export interface BusinessListingCriteria extends ListCriteria {
|
||||
criteriaType: 'business';
|
||||
}
|
||||
export interface CommercialPropertyListingCriteria extends ListCriteria {
|
||||
state: string;
|
||||
county: string;
|
||||
minPrice: number;
|
||||
maxPrice: number;
|
||||
@@ -95,7 +96,6 @@ export interface UserListingCriteria extends ListCriteria {
|
||||
lastname: string;
|
||||
companyName: string;
|
||||
counties: string[];
|
||||
states: string[];
|
||||
criteriaType: 'broker';
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DrizzleModule } from '../drizzle/drizzle.module.js';
|
||||
import { FileService } from '../file/file.service.js';
|
||||
import { GeoModule } from '../geo/geo.module.js';
|
||||
import { GeoService } from '../geo/geo.service.js';
|
||||
import { UserController } from './user.controller.js';
|
||||
import { UserService } from './user.service.js';
|
||||
|
||||
@Module({
|
||||
imports: [DrizzleModule],
|
||||
imports: [DrizzleModule, GeoModule],
|
||||
controllers: [UserController],
|
||||
providers: [UserService, FileService],
|
||||
providers: [UserService, FileService, GeoService],
|
||||
})
|
||||
export class UserModule {}
|
||||
|
||||
@@ -6,8 +6,10 @@ import { Logger } from 'winston';
|
||||
import * as schema from '../drizzle/schema.js';
|
||||
import { customerSubTypeEnum, PG_CONNECTION } from '../drizzle/schema.js';
|
||||
import { FileService } from '../file/file.service.js';
|
||||
import { GeoService } from '../geo/geo.service.js';
|
||||
import { User } from '../models/db.model.js';
|
||||
import { emailToDirName, JwtUser, UserListingCriteria } from '../models/main.model.js';
|
||||
import { getDistanceQuery } from '../utils.js';
|
||||
|
||||
type CustomerSubType = (typeof customerSubTypeEnum.enumValues)[number];
|
||||
@Injectable()
|
||||
@@ -16,6 +18,7 @@ export class UserService {
|
||||
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
||||
@Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,
|
||||
private fileService: FileService,
|
||||
private geoService: GeoService,
|
||||
) {}
|
||||
// private getConditions(criteria: UserListingCriteria): any[] {
|
||||
// const conditions = [];
|
||||
@@ -32,10 +35,13 @@ export class UserService {
|
||||
private getWhereConditions(criteria: UserListingCriteria): SQL[] {
|
||||
const whereConditions: SQL[] = [];
|
||||
|
||||
if (criteria.city) {
|
||||
if (criteria.city && criteria.searchType === 'exact') {
|
||||
whereConditions.push(ilike(schema.users.companyLocation, `%${criteria.city}%`));
|
||||
}
|
||||
|
||||
if (criteria.city && criteria.radius && criteria.searchType === 'radius' && criteria.radius) {
|
||||
const cityGeo = this.geoService.getCityWithCoords(criteria.state, criteria.city);
|
||||
whereConditions.push(sql`${getDistanceQuery(schema.users, parseFloat(cityGeo.latitude), parseFloat(cityGeo.longitude))} <= ${criteria.radius}`);
|
||||
}
|
||||
if (criteria.types && criteria.types.length > 0) {
|
||||
// whereConditions.push(inArray(schema.users.customerSubType, criteria.types));
|
||||
whereConditions.push(inArray(schema.users.customerSubType, criteria.types as CustomerSubType[]));
|
||||
@@ -57,10 +63,12 @@ export class UserService {
|
||||
whereConditions.push(or(...criteria.counties.map(county => sql`EXISTS (SELECT 1 FROM jsonb_array_elements(${schema.users.areasServed}) AS area WHERE area->>'county' ILIKE ${`%${county}%`})`)));
|
||||
}
|
||||
|
||||
if (criteria.states && criteria.states.length > 0) {
|
||||
whereConditions.push(or(...criteria.states.map(state => sql`EXISTS (SELECT 1 FROM jsonb_array_elements(${schema.users.areasServed}) AS area WHERE area->>'state' = ${state})`)));
|
||||
// if (criteria.states && criteria.states.length > 0) {
|
||||
// whereConditions.push(or(...criteria.states.map(state => sql`EXISTS (SELECT 1 FROM jsonb_array_elements(${schema.users.areasServed}) AS area WHERE area->>'state' = ${state})`)));
|
||||
// }
|
||||
if (criteria.state) {
|
||||
whereConditions.push(sql`EXISTS (SELECT 1 FROM jsonb_array_elements(${schema.users.areasServed}) AS area WHERE area->>'state' = ${criteria.state})`);
|
||||
}
|
||||
|
||||
return whereConditions;
|
||||
}
|
||||
async searchUserListings(criteria: UserListingCriteria) {
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { businesses, commercials, users } from './drizzle/schema.js';
|
||||
export const EARTH_RADIUS_KM = 6371; // Erdradius in Kilometern
|
||||
export const EARTH_RADIUS_MILES = 3959; // Erdradius in Meilen
|
||||
|
||||
export function convertStringToNullUndefined(value) {
|
||||
// Konvertiert den Wert zu Kleinbuchstaben für eine case-insensitive Überprüfung
|
||||
const lowerCaseValue = typeof value === 'boolean' ? value : value?.toLowerCase();
|
||||
@@ -10,4 +15,16 @@ export function convertStringToNullUndefined(value) {
|
||||
|
||||
// Gibt den Originalwert zurück, wenn es sich nicht um 'null' oder 'undefined' handelt
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
export const getDistanceQuery = (schema: typeof businesses | typeof commercials | typeof users, lat: number, lon: number, unit: 'km' | 'miles' = 'miles') => {
|
||||
const radius = unit === 'km' ? EARTH_RADIUS_KM : EARTH_RADIUS_MILES;
|
||||
|
||||
return sql`
|
||||
${radius} * 2 * ASIN(SQRT(
|
||||
POWER(SIN((${lat} - ${schema.latitude}) * PI() / 180 / 2), 2) +
|
||||
COS(${lat} * PI() / 180) * COS(${schema.latitude} * PI() / 180) *
|
||||
POWER(SIN((${lon} - ${schema.longitude}) * PI() / 180 / 2), 2)
|
||||
))
|
||||
`;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user