CI Pipeline / backend-test (push) Successful in 8m58s
CI Pipeline / frontend-test (push) Successful in 29s
CI Pipeline / frontend-build (push) Successful in 31s
CI Pipeline / backend-lint (push) Successful in 10s
CI Pipeline / backend-test (pull_request) Successful in 8m56s
CI Pipeline / frontend-test (pull_request) Successful in 28s
CI Pipeline / frontend-build (pull_request) Successful in 32s
CI Pipeline / backend-lint (pull_request) Successful in 11s
CI Pipeline / quality-gate (push) Successful in 3s
CI Pipeline / quality-gate (pull_request) Successful in 3s
The reviewer noted the static sitemap.xml had a hardcoded domain in a white-label app. Instead of writing files to disk, we now serve sitemap.xml and robots.txt as dynamic API endpoints that read KMTN_WEBSITE_URL at runtime. - Nginx proxies /sitemap.xml and /robots.txt to the backend API so search crawlers reach them at root paths. - Removed the startup filesystem-write that would fail in Docker (backend can't write to Nginx container's static dir). - Simplified sitemap_export.py to pure generation functions (no I/O). - Added /api/sitemap/regenerate admin endpoint for consistency.
53 lines
1.8 KiB
Python
53 lines
1.8 KiB
Python
"""Sitemap & robots.txt generation: tenant-aware SEO content.
|
|
|
|
Because this is a white-label app where each tenant has its own domain
|
|
(set via KMTN_WEBSITE_URL), the sitemap and robots.txt are generated
|
|
at runtime — no static files are needed. The FastAPI /api/sitemap/*
|
|
endpoints call these functions; Nginx proxies /sitemap.xml and
|
|
/robots.txt to those endpoints so search-engine crawlers hit the right
|
|
content at the root URL.
|
|
"""
|
|
|
|
# Public routes included in the sitemap with their change frequency and priority.
|
|
SEOMAP_ROUTES = [
|
|
{"path": "/", "changefreq": "daily", "priority": 1.0},
|
|
{"path": "/about", "changefreq": "weekly", "priority": 0.8},
|
|
{"path": "/schedule", "changefreq": "weekly", "priority": 0.8},
|
|
{"path": "/events", "changefreq": "daily", "priority": 0.8},
|
|
{"path": "/contact", "changefreq": "monthly", "priority": 0.5},
|
|
]
|
|
|
|
|
|
def build_sitemap_xml(base_url: str) -> str:
|
|
"""Build a valid sitemap.xml string for the given base URL."""
|
|
url = base_url.rstrip("/")
|
|
entries = []
|
|
for route in SEOMAP_ROUTES:
|
|
entries.append(
|
|
f" <url>\n"
|
|
f" <loc>{url}{route['path']}</loc>\n"
|
|
f" <changefreq>{route['changefreq']}</changefreq>\n"
|
|
f" <priority>{route['priority']}</priority>\n"
|
|
f" </url>"
|
|
)
|
|
return (
|
|
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
|
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
|
|
+ "\n".join(entries) + "\n"
|
|
"</urlset>\n"
|
|
)
|
|
|
|
|
|
def build_robots_txt(base_url: str) -> str:
|
|
"""Build a robots.txt string that references the correct sitemap URL."""
|
|
sitemap = base_url.rstrip("/") + "/sitemap.xml"
|
|
return (
|
|
"User-agent: *\n"
|
|
"Allow: /\n"
|
|
"\n"
|
|
"Disallow: /admin\n"
|
|
"Disallow: /login\n"
|
|
"\n"
|
|
f"Sitemap: {sitemap}\n"
|
|
)
|