Files
kmtnflower/backend/app/sitemap_export.py
T
Hermes Robot 3dead2921a
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
fix: switch sitemap/robots to dynamic generation + Nginx proxy
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.
2026-08-04 20:06:00 +00:00

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"
)