from __future__ import annotations from fastapi import APIRouter, HTTPException, Request from fastapi.responses import HTMLResponse, PlainTextResponse, RedirectResponse, Response from starlette.concurrency import run_in_threadpool from ..ddns_security import client_ip from ..dependencies import ServiceDep, SettingsDep from ..web.forms import first_form_value, request_form from ..web.templates_public import _render_privacy_page, _render_public_home router = APIRouter() @router.get("/", response_class=HTMLResponse, include_in_schema=False) def index(settings: SettingsDep, service: ServiceDep) -> str: return _render_public_home(service, settings, None, None) @router.get("/magic", include_in_schema=False) def magic_form() -> RedirectResponse: return RedirectResponse("/#create", status_code=303) @router.post("/magic", response_class=HTMLResponse, include_in_schema=False) async def magic_account(request: Request, settings: SettingsDep, service: ServiceDep) -> str: await request_form(request, settings) account = await run_in_threadpool(service.create_managed_account, None) return _render_public_home(service, settings, account, None) @router.post("/accounts", response_class=HTMLResponse, include_in_schema=False) async def self_service_account(request: Request, settings: SettingsDep, service: ServiceDep) -> str: form = await request_form(request, settings) mode = first_form_value(form, "mode") if mode and mode != "managed": raise HTTPException(status_code=400, detail="custom hostnames are not supported; create a KarlDNS hostname and use a CNAME") if first_form_value(form, "hostname") or first_form_value(form, "claim_secret"): raise HTTPException(status_code=400, detail="custom hostnames are not supported; create a KarlDNS hostname and use a CNAME") account = await run_in_threadpool(service.create_managed_account, None) return _render_public_home(service, settings, account, None) @router.get("/healthz", response_class=PlainTextResponse) def healthz() -> str: return "ok" @router.get("/ip", response_class=PlainTextResponse) def visible_ip(request: Request, settings: SettingsDep) -> str: return client_ip(request, settings) @router.get("/privacy", response_class=HTMLResponse, include_in_schema=False) def privacy(settings: SettingsDep) -> str: return _render_privacy_page(settings) @router.get("/robots.txt", response_class=PlainTextResponse, include_in_schema=False) def robots_txt(settings: SettingsDep) -> str: base_url = settings.public_base_url.rstrip("/") admin_path = settings.admin_path.rstrip("/") return "\n".join( [ "User-agent: *", f"Disallow: {admin_path}/", "Disallow: /m/", "Disallow: /u/", "Disallow: /nic/", "Disallow: /api/", "Disallow: /docs", "Disallow: /redoc", "Disallow: /openapi.json", f"Sitemap: {base_url}/sitemap.xml", "", ] ) @router.get("/sitemap.xml", include_in_schema=False) def sitemap_xml(settings: SettingsDep) -> Response: base_url = settings.public_base_url.rstrip("/") body = f""" {base_url}/ weekly 1.0 {base_url}/privacy monthly 0.3 """ return Response(body, media_type="application/xml")