Add Schema.org JSON-LD, SEO meta overrides, and resilient bulk SMS.

Expose Organization/LocalBusiness and Product/BlogPosting jsonLd, optional seoMeta fields on content/categories, and continue mass SMS after per-recipient failures.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-09-09 23:30:24 +03:30
co-authored by Cursor
parent db305c58a4
commit aaf7a83742
33 changed files with 1395 additions and 188 deletions
+49 -12
View File
@@ -4,6 +4,8 @@ import {
normalizePhoneNumbers,
normalizeSocialMedia,
} from '../business-profile/business-profile.util';
import { normalizeBusinessSettings } from '../business-settings/business-settings.util';
import { buildWebsiteSchemaJsonLd } from '../business-settings/website-schema.util';
import { PrismaService } from '../prisma/prisma.service';
import { TenantService } from '../tenant/tenant.service';
@@ -16,6 +18,7 @@ export class WebsiteBusinessInfoService {
async getPublic(host: string) {
const business = await this.tenant.resolveBusinessByDomain(host);
const settings = normalizeBusinessSettings(business.settings);
const record = await this.prisma.business.findUnique({
where: { id: business.id },
@@ -31,27 +34,61 @@ export class WebsiteBusinessInfoService {
throw new NotFoundException('Business not found');
}
const emails = normalizeEmails(record.emails);
const phoneNumbers = normalizePhoneNumbers(record.phoneNumbers);
const socialMedia = normalizeSocialMedia(record.socialMedia);
const addresses = record.addresses.map((address) => ({
id: address.id.toString(),
province: address.province,
city: address.city,
address: address.address,
postalCode: address.postalCode,
landline: address.landline,
}));
const logoUrl = record.logoMedia?.publicUrl ?? null;
const normalizedHost = host
.toLowerCase()
.trim()
.replace(/^(customer|business)\./, '');
const schemaSettings = settings.website.schema;
const jsonLd = buildWebsiteSchemaJsonLd({
settings: schemaSettings,
host: normalizedHost,
name: record.name,
nameFa: record.nameFa ?? '',
about: record.about ?? '',
logoUrl,
emails,
phoneNumbers,
socialMedia,
addresses,
});
return {
id: record.id.toString(),
name: record.name,
nameFa: record.nameFa ?? '',
about: record.about ?? '',
vision: record.vision ?? '',
logoUrl: record.logoMedia?.publicUrl ?? null,
logoUrl,
logoDarkUrl: record.logoDarkMedia?.publicUrl ?? null,
faviconUrl:
record.faviconMedia?.publicUrl ?? record.logoMedia?.publicUrl ?? null,
emails: normalizeEmails(record.emails),
phoneNumbers: normalizePhoneNumbers(record.phoneNumbers),
socialMedia: normalizeSocialMedia(record.socialMedia),
addresses: record.addresses.map((address) => ({
id: address.id.toString(),
province: address.province,
city: address.city,
address: address.address,
postalCode: address.postalCode,
landline: address.landline,
})),
emails,
phoneNumbers,
socialMedia,
addresses,
schema: {
enabled: schemaSettings.enabled,
type: schemaSettings.type,
name: schemaSettings.name,
nameFa: schemaSettings.nameFa,
description: schemaSettings.description,
priceRange: schemaSettings.priceRange,
sameAs: schemaSettings.sameAs,
jsonLd,
},
};
}
}