"""سئوی سمت سرور — robots.txt، sitemap.xml و سرو HTML صفحات وبلاگ/پروژه با متای تزریقشده. فرانتاند یک SPA است؛ خزندههای اجتماعی (تلگرام، واتساپ و...) جاوااسکریپت اجرا نمیکنند. این ماژول همان index.html بیلد فرانتاند را میخواند و پیش از ارسال، تگهای title / description / OG / canonical / JSON-LD را از دیتابیس داخل آن تزریق میکند — بازدیدکنندههای عادی همان SPA همیشگی را میبینند و خزندهها متای درست را میخوانند. """ import html import json import re from pathlib import Path from fastapi import APIRouter, Depends, Response from fastapi.responses import HTMLResponse from sqlalchemy.orm import Session from . import models from .config import settings from .database import get_db router = APIRouter() STATIC_PATHS = ["/", "/about", "/services", "/projects", "/investment", "/blog", "/faq", "/contact"] def _absolute(path: str) -> str: if not path: return "" if path.startswith(("http://", "https://")): return path site = settings.site_url.rstrip("/") return f"{site}{path}" if site else path def _read_index() -> str | None: try: return (Path(settings.frontend_dist) / "index.html").read_text(encoding="utf-8") except OSError: return None def _doc_text(doc: object, limit: int = 300) -> str: """متن ساده سند TipTap — برای noscript و پیشنمایش.""" words: list[str] = [] def walk(node: object) -> None: if not isinstance(node, dict): return if node.get("type") == "text" and isinstance(node.get("text"), str): words.append(node["text"]) for child in node.get("content") or []: walk(child) if isinstance(doc, dict): for block in doc.get("content") or []: walk(block) text = " ".join(words).strip() return text[:limit] + ("…" if len(text) > limit else "") def _shell( *, title: str, description: str, path: str, og_image: str = "", og_type: str = "website", jsonld: dict | None = None, noscript: str = "", ) -> HTMLResponse: """index.html فرانتاند با متای تزریقشده؛ اگر بیلد در دسترس نبود، صفحه حداقلی.""" title = title.strip() or "گروه ساختمانی آرامش" description = description.strip() canonical = _absolute(path) image = _absolute(og_image) esc = html.escape # تگ title جای تگ موجود index.html را میگیرد و در بلوک تکرار نمیشود tags = [ f'', f'', f'', f'', f'', f'', ] if image: tags.append(f'') if jsonld: payload = json.dumps(jsonld, ensure_ascii=False).replace("", "<\\/") tags.append(f'') block = "\n ".join(tags) raw = _read_index() if raw is None: # بیلد فرانتاند سوار نشده — صفحه حداقلی با خودِ متا تا خزندهها دست خالی نمانند body = f"
{esc(description)}
" return HTMLResponse( f'' f"