feat: Initialize Angular SSR application with core pages, components, and server setup.
This commit is contained in:
0
bizmatch-server/prod.dump
Executable file → Normal file
0
bizmatch-server/prod.dump
Executable file → Normal file
@@ -31,20 +31,35 @@ export class BusinessListingService {
|
||||
const cityGeo = this.geoService.getCityWithCoords(criteria.state, criteria.city.name);
|
||||
whereConditions.push(sql`${getDistanceQuery(businesses_json, cityGeo.latitude, cityGeo.longitude)} <= ${criteria.radius}`);
|
||||
}
|
||||
if (criteria.types && criteria.types.length > 0) {
|
||||
whereConditions.push(inArray(sql`${businesses_json.data}->>'type'`, criteria.types));
|
||||
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.state) {
|
||||
whereConditions.push(sql`(${businesses_json.data}->'location'->>'state') = ${criteria.state}`);
|
||||
}
|
||||
|
||||
if (criteria.minPrice) {
|
||||
whereConditions.push(gte(sql`(${businesses_json.data}->>'price')::double precision`, criteria.minPrice));
|
||||
if (criteria.minPrice !== undefined && criteria.minPrice !== null) {
|
||||
whereConditions.push(
|
||||
and(
|
||||
sql`(${businesses_json.data}->>'price') IS NOT NULL`,
|
||||
sql`(${businesses_json.data}->>'price') != ''`,
|
||||
gte(sql`REPLACE(${businesses_json.data}->>'price', ',', '')::double precision`, criteria.minPrice)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (criteria.maxPrice) {
|
||||
whereConditions.push(lte(sql`(${businesses_json.data}->>'price')::double precision`, criteria.maxPrice));
|
||||
if (criteria.maxPrice !== undefined && criteria.maxPrice !== null) {
|
||||
whereConditions.push(
|
||||
and(
|
||||
sql`(${businesses_json.data}->>'price') IS NOT NULL`,
|
||||
sql`(${businesses_json.data}->>'price') != ''`,
|
||||
lte(sql`REPLACE(${businesses_json.data}->>'price', ',', '')::double precision`, criteria.maxPrice)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (criteria.minRevenue) {
|
||||
@@ -87,8 +102,14 @@ export class BusinessListingService {
|
||||
whereConditions.push(eq(sql`(${businesses_json.data}->>'franchiseResale')::boolean`, criteria.franchiseResale));
|
||||
}
|
||||
|
||||
if (criteria.title) {
|
||||
whereConditions.push(sql`(${businesses_json.data}->>'title') ILIKE ${`%${criteria.title}%`} OR (${businesses_json.data}->>'description') ILIKE ${`%${criteria.title}%`}`);
|
||||
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}`
|
||||
)
|
||||
);
|
||||
}
|
||||
if (criteria.brokerName) {
|
||||
const { firstname, lastname } = splitName(criteria.brokerName);
|
||||
@@ -122,9 +143,16 @@ export class BusinessListingService {
|
||||
|
||||
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 });
|
||||
|
||||
if (whereConditions.length > 0) {
|
||||
const whereClause = and(...whereConditions);
|
||||
query.where(whereClause);
|
||||
|
||||
// Uncomment for debugging SQL queries:
|
||||
// this.logger.info('Generated SQL:', { sql: query.toSQL() });
|
||||
}
|
||||
|
||||
// Sortierung
|
||||
|
||||
@@ -3,7 +3,7 @@ import { SitemapService } from './sitemap.service';
|
||||
|
||||
@Controller()
|
||||
export class SitemapController {
|
||||
constructor(private readonly sitemapService: SitemapService) {}
|
||||
constructor(private readonly sitemapService: SitemapService) { }
|
||||
|
||||
/**
|
||||
* Main sitemap index - lists all sitemap files
|
||||
@@ -48,4 +48,15 @@ export class SitemapController {
|
||||
async getCommercialSitemap(@Param('page', ParseIntPipe) page: number): Promise<string> {
|
||||
return await this.sitemapService.generateCommercialSitemap(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broker profiles sitemap (paginated)
|
||||
* Route: /sitemap/brokers-1.xml, /sitemap/brokers-2.xml, etc.
|
||||
*/
|
||||
@Get('sitemap/brokers-:page.xml')
|
||||
@Header('Content-Type', 'application/xml')
|
||||
@Header('Cache-Control', 'public, max-age=3600')
|
||||
async getBrokerSitemap(@Param('page', ParseIntPipe) page: number): Promise<string> {
|
||||
return await this.sitemapService.generateBrokerSitemap(page);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export class SitemapService {
|
||||
private readonly baseUrl = 'https://biz-match.com';
|
||||
private readonly URLS_PER_SITEMAP = 10000; // Google best practice
|
||||
|
||||
constructor(@Inject(PG_CONNECTION) private readonly db: NodePgDatabase<typeof schema>) {}
|
||||
constructor(@Inject(PG_CONNECTION) private readonly db: NodePgDatabase<typeof schema>) { }
|
||||
|
||||
/**
|
||||
* Generate sitemap index (main sitemap.xml)
|
||||
@@ -32,26 +32,36 @@ export class SitemapService {
|
||||
|
||||
// Add static pages sitemap
|
||||
sitemaps.push({
|
||||
loc: `${this.baseUrl}/sitemap/static.xml`,
|
||||
loc: `${this.baseUrl}/bizmatch/sitemap/static.xml`,
|
||||
lastmod: this.formatDate(new Date()),
|
||||
});
|
||||
|
||||
// Count business listings
|
||||
const businessCount = await this.getBusinessListingsCount();
|
||||
const businessPages = Math.ceil(businessCount / this.URLS_PER_SITEMAP);
|
||||
const businessPages = Math.ceil(businessCount / this.URLS_PER_SITEMAP) || 1;
|
||||
for (let page = 1; page <= businessPages; page++) {
|
||||
sitemaps.push({
|
||||
loc: `${this.baseUrl}/sitemap/business-${page}.xml`,
|
||||
loc: `${this.baseUrl}/bizmatch/sitemap/business-${page}.xml`,
|
||||
lastmod: this.formatDate(new Date()),
|
||||
});
|
||||
}
|
||||
|
||||
// Count commercial property listings
|
||||
const commercialCount = await this.getCommercialPropertiesCount();
|
||||
const commercialPages = Math.ceil(commercialCount / this.URLS_PER_SITEMAP);
|
||||
const commercialPages = Math.ceil(commercialCount / this.URLS_PER_SITEMAP) || 1;
|
||||
for (let page = 1; page <= commercialPages; page++) {
|
||||
sitemaps.push({
|
||||
loc: `${this.baseUrl}/sitemap/commercial-${page}.xml`,
|
||||
loc: `${this.baseUrl}/bizmatch/sitemap/commercial-${page}.xml`,
|
||||
lastmod: this.formatDate(new Date()),
|
||||
});
|
||||
}
|
||||
|
||||
// Count broker profiles
|
||||
const brokerCount = await this.getBrokerProfilesCount();
|
||||
const brokerPages = Math.ceil(brokerCount / this.URLS_PER_SITEMAP) || 1;
|
||||
for (let page = 1; page <= brokerPages; page++) {
|
||||
sitemaps.push({
|
||||
loc: `${this.baseUrl}/bizmatch/sitemap/brokers-${page}.xml`,
|
||||
lastmod: this.formatDate(new Date()),
|
||||
});
|
||||
}
|
||||
@@ -289,4 +299,64 @@ ${sitemapElements}
|
||||
const d = typeof date === 'string' ? new Date(date) : date;
|
||||
return d.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate broker profiles sitemap (paginated)
|
||||
*/
|
||||
async generateBrokerSitemap(page: number): Promise<string> {
|
||||
const offset = (page - 1) * this.URLS_PER_SITEMAP;
|
||||
const urls = await this.getBrokerProfileUrls(offset, this.URLS_PER_SITEMAP);
|
||||
return this.buildXmlSitemap(urls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count broker profiles (professionals with showInDirectory=true)
|
||||
*/
|
||||
private async getBrokerProfilesCount(): Promise<number> {
|
||||
try {
|
||||
const result = await this.db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(schema.users_json)
|
||||
.where(sql`
|
||||
(${schema.users_json.data}->>'customerType') = 'professional'
|
||||
AND (${schema.users_json.data}->>'showInDirectory')::boolean IS TRUE
|
||||
`);
|
||||
|
||||
return Number(result[0]?.count || 0);
|
||||
} catch (error) {
|
||||
console.error('Error counting broker profiles:', error);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get broker profile URLs from database (paginated)
|
||||
*/
|
||||
private async getBrokerProfileUrls(offset: number, limit: number): Promise<SitemapUrl[]> {
|
||||
try {
|
||||
const brokers = await this.db
|
||||
.select({
|
||||
email: schema.users_json.email,
|
||||
updated: sql<Date>`(${schema.users_json.data}->>'updated')::timestamptz`,
|
||||
created: sql<Date>`(${schema.users_json.data}->>'created')::timestamptz`,
|
||||
})
|
||||
.from(schema.users_json)
|
||||
.where(sql`
|
||||
(${schema.users_json.data}->>'customerType') = 'professional'
|
||||
AND (${schema.users_json.data}->>'showInDirectory')::boolean IS TRUE
|
||||
`)
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
|
||||
return brokers.map(broker => ({
|
||||
loc: `${this.baseUrl}/details-user/${encodeURIComponent(broker.email)}`,
|
||||
lastmod: this.formatDate(broker.updated || broker.created),
|
||||
changefreq: 'weekly' as const,
|
||||
priority: 0.7,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error fetching broker profiles for sitemap:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user