79 lines
2.4 KiB
Python
79 lines
2.4 KiB
Python
"""SEO helpers for sitemap and robots.txt."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timedelta
|
|
from typing import List
|
|
|
|
from flask import Response, current_app, url_for
|
|
|
|
from cmdforge.registry.db import connect_db, query_all
|
|
|
|
SITEMAP_TTL = timedelta(hours=6)
|
|
_sitemap_cache = {"generated_at": None, "xml": ""}
|
|
|
|
|
|
def generate_sitemap() -> str:
|
|
now = datetime.utcnow()
|
|
cached_at = _sitemap_cache.get("generated_at")
|
|
if cached_at and now - cached_at < SITEMAP_TTL:
|
|
return _sitemap_cache["xml"]
|
|
|
|
urls: List[str] = []
|
|
static_paths = ["/", "/tools", "/docs", "/tutorials", "/about"]
|
|
for path in static_paths:
|
|
urls.append(_url_entry(path, "daily", "1.0"))
|
|
|
|
conn = connect_db()
|
|
try:
|
|
rows = query_all(conn, "SELECT DISTINCT owner, name FROM tools")
|
|
for row in rows:
|
|
tool_path = url_for("web.tool_detail", owner=row["owner"], name=row["name"], _external=True)
|
|
urls.append(_url_entry(tool_path, "daily", "0.9"))
|
|
|
|
categories = query_all(conn, "SELECT DISTINCT category FROM tools WHERE category IS NOT NULL")
|
|
for row in categories:
|
|
cat_path = url_for("web.category", name=row["category"], _external=True)
|
|
urls.append(_url_entry(cat_path, "weekly", "0.7"))
|
|
finally:
|
|
conn.close()
|
|
|
|
xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
|
xml += "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n"
|
|
xml += "\n".join(urls)
|
|
xml += "\n</urlset>\n"
|
|
|
|
_sitemap_cache["generated_at"] = now
|
|
_sitemap_cache["xml"] = xml
|
|
return xml
|
|
|
|
|
|
def sitemap_response() -> Response:
|
|
xml = generate_sitemap()
|
|
return Response(xml, mimetype="application/xml")
|
|
|
|
|
|
def robots_txt() -> Response:
|
|
lines = [
|
|
"User-agent: *",
|
|
"Allow: /",
|
|
"Disallow: /login",
|
|
"Disallow: /register",
|
|
"Disallow: /dashboard",
|
|
"Disallow: /api/",
|
|
f"Sitemap: {url_for('web.sitemap', _external=True)}",
|
|
]
|
|
return Response("\n".join(lines) + "\n", mimetype="text/plain")
|
|
|
|
|
|
def _url_entry(loc: str, changefreq: str, priority: str) -> str:
|
|
if not loc.startswith("http"):
|
|
loc = url_for("web.index", _external=True).rstrip("/") + loc
|
|
return "\n".join([
|
|
" <url>",
|
|
f" <loc>{loc}</loc>",
|
|
f" <changefreq>{changefreq}</changefreq>",
|
|
f" <priority>{priority}</priority>",
|
|
" </url>",
|
|
])
|