Initial commit: Visigine (Vite client + Express/SQLite backend)
Container-ready via docker/ compose (frontend nginx + backend Node). Compose adjusted for Coolify on the prod server: frontend uses expose:80 (no host binding — host 8080 is taken by the Coolify proxy; Traefik routes visigine.de), backend ALLOWED_ORIGINS=https://visigine.de. Secrets stay in server/.env (git-ignored); see server/.env.example. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
79
server/lib/autofix/json-ld.js
Normal file
79
server/lib/autofix/json-ld.js
Normal file
@@ -0,0 +1,79 @@
|
||||
// Generates a JSON-LD skeleton: Organization (or LocalBusiness if address/phone),
|
||||
// WebSite, and FAQPage — three highest-impact AI signals.
|
||||
|
||||
const ph = (s) => `[Bitte ergänzen: ${s}]`
|
||||
|
||||
function buildOrganizationNode(siteData) {
|
||||
const { name, url, description, email, phone, address, sameAs = [] } = siteData
|
||||
const useLocalBusiness = Boolean(address || phone)
|
||||
|
||||
const node = {
|
||||
'@type': useLocalBusiness ? 'LocalBusiness' : 'Organization',
|
||||
'@id': `${(url || ph('https://deine-domain.de')).replace(/\/+$/, '')}/#organization`,
|
||||
name: name || ph('Name deines Unternehmens'),
|
||||
url: url || ph('https://deine-domain.de'),
|
||||
description: description || ph('Ein-Satz-Beschreibung'),
|
||||
}
|
||||
|
||||
if (email) node.email = email
|
||||
if (phone) node.telephone = phone
|
||||
|
||||
node.address = {
|
||||
'@type': 'PostalAddress',
|
||||
addressCountry: address?.addressCountry || ph('DE/AT/CH'),
|
||||
addressLocality: address?.addressLocality || ph('Stadt'),
|
||||
postalCode: address?.postalCode || ph('PLZ'),
|
||||
streetAddress: address?.streetAddress || ph('Straße + Nr.'),
|
||||
}
|
||||
|
||||
node.sameAs = sameAs.length > 0 ? sameAs : [ph('https://www.linkedin.com/company/...')]
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
function buildWebSiteNode(siteData) {
|
||||
const { name, url, language = 'de' } = siteData
|
||||
return {
|
||||
'@type': 'WebSite',
|
||||
'@id': `${(url || ph('https://deine-domain.de')).replace(/\/+$/, '')}/#website`,
|
||||
url: url || ph('https://deine-domain.de'),
|
||||
name: name || ph('Name deines Unternehmens'),
|
||||
inLanguage: `${language}-DE`,
|
||||
publisher: { '@id': `${(url || ph('https://deine-domain.de')).replace(/\/+$/, '')}/#organization` },
|
||||
}
|
||||
}
|
||||
|
||||
function buildFaqNode() {
|
||||
return {
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: [
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: ph('häufige Frage'),
|
||||
acceptedAnswer: { '@type': 'Answer', text: ph('1-2 Sätze') },
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: ph('weitere Frage'),
|
||||
acceptedAnswer: { '@type': 'Answer', text: ph('1-2 Sätze') },
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export function generateJsonLd(siteData) {
|
||||
const payload = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
buildOrganizationNode(siteData),
|
||||
buildWebSiteNode(siteData),
|
||||
buildFaqNode(),
|
||||
],
|
||||
}
|
||||
const pretty = JSON.stringify(payload, null, 2)
|
||||
const content = `<script type="application/ld+json">\n${pretty}\n</script>\n`
|
||||
return {
|
||||
content,
|
||||
mode: siteData.hasOrgJsonLd ? 'enhance' : 'new',
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user