pig.py/serp/app.py
Russell Ballestrini 0974593873 Refactor language handling to FastAPI Depends
- Replace get_lang() calls with Depends(get_language) dependency
- Language resolution: ?lang=XX query > cookie > Accept-Language header
- Middleware sets cookie, frontend cleans URL via history.replaceState
- view_page_by_hash now redirects instead of direct call
2026-01-06 08:25:50 -05:00

115 lines
3.6 KiB
Python

"""FastAPI application setup for SERP.
# Side quest 4/21: The rabbit hole goes deeper than you think.
# Down the rabbit whole, Alice - Gulp!
#
# If you're reading this, you understand Python.
# The source code for neopig is Public Domain. Steal what works for you!
#
# - The Sign Maker
"""
import logging
import os
from pathlib import Path
from fastapi import FastAPI, Request, Cookie, Header
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from .i18n import TRANSLATIONS
logger = logging.getLogger(__name__)
# Create app
app = FastAPI(title="neopig", description="Media crawler + SERP + Screenshot service")
# Supported language codes
SUPPORTED_LANGS = set(TRANSLATIONS.keys())
async def get_language(
request: Request,
lang: str = Cookie(None),
accept_language: str = Header(None)
) -> str:
"""Dependency: resolve language from ?lang=XX > cookie > Accept-Language.
Easter egg #0: Use ?lang=XX to force any page into a specific language.
Cookie is set via middleware (LanguageCookieMiddleware).
"""
# Query param takes priority
lang_query = request.query_params.get("lang")
if lang_query and lang_query.lower() in SUPPORTED_LANGS:
return lang_query.lower()
# Cookie next
if lang and lang in SUPPORTED_LANGS:
return lang
# Accept-Language header
if accept_language:
for part in accept_language.split(','):
code = part.split(';')[0].strip().split('-')[0].lower()
if code in SUPPORTED_LANGS:
return code
return "en"
from starlette.middleware.base import BaseHTTPMiddleware
class LanguageCookieMiddleware(BaseHTTPMiddleware):
"""Middleware to set lang cookie when ?lang=XX is used."""
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
# Set cookie if ?lang= query param is present and valid
lang_query = request.query_params.get("lang")
if lang_query and lang_query.lower() in SUPPORTED_LANGS:
response.set_cookie(
key="lang",
value=lang_query.lower(),
max_age=365 * 24 * 60 * 60,
httponly=False,
samesite="lax"
)
return response
app.add_middleware(LanguageCookieMiddleware)
# Paths relative to serp.py location (parent of this package)
BASE_PATH = Path(__file__).parent.parent
# Set up Jinja2 templates
TEMPLATES_PATH = BASE_PATH / "templates"
templates = Jinja2Templates(directory=str(TEMPLATES_PATH)) if TEMPLATES_PATH.exists() else None
# Try to include uri2png screenshot router (optional)
try:
from uri2png import get_screenshot_router
app.include_router(get_screenshot_router())
logger.info("Screenshot router loaded from uri2png")
except ImportError:
pass # Optional dependency
# Mount static files
STATIC_CSS_PATH = BASE_PATH / "static" / "css"
if STATIC_CSS_PATH.exists():
app.mount("/static/css", StaticFiles(directory=STATIC_CSS_PATH), name="css")
STATIC_VENDOR_PATH = BASE_PATH / "static" / "vendor"
if STATIC_VENDOR_PATH.exists():
app.mount("/static/vendor", StaticFiles(directory=STATIC_VENDOR_PATH), name="vendor")
STATIC_IMAGES_PATH = BASE_PATH / "static" / "images"
if STATIC_IMAGES_PATH.exists():
app.mount("/static/images", StaticFiles(directory=STATIC_IMAGES_PATH), name="images")
# Config
DB_PATH = "data/neopig.db"
VAULT_PATH = Path("data/vault")
CRAWL_DISABLED = os.environ.get("NEOPIG_DISABLE_CRAWL", "").lower() in ("1", "true", "yes")
IMPORT_MODE = os.environ.get("NEOPIG_IMPORT", "").lower() in ("1", "true", "yes")