"""TTS Voice Creator - Clone and Design — FastAPI entry point (thin wrapper).""" from __future__ import annotations import logging import os from logging.handlers import RotatingFileHandler from pathlib import Path from fastapi import FastAPI, Request from fastapi.responses import Response from fastapi.staticfiles import StaticFiles from core.constants import STATIC_DIR, _BufferHandler from routes import admin, settings, library, stt, sources, docker, tts, conversation logger = logging.getLogger("uvicorn.error") # ── App ─────────────────────────────────────────────────────────────────────── app = FastAPI(title="TTS Voice Creator - Clone and Design") # ── In-memory log buffer handler ────────────────────────────────────────────── _buf_handler = _BufferHandler() _buf_handler.setLevel(logging.DEBUG) logging.getLogger().addHandler(_buf_handler) # ── File log handler (rotates at 5 MB, keeps 3 files) ──────────────────────── _LOG_DIR = Path(os.environ.get("LOG_DIR", "/logs")) try: _LOG_DIR.mkdir(parents=True, exist_ok=True) _file_handler = RotatingFileHandler( _LOG_DIR / "app.log", maxBytes=5 * 1024 * 1024, backupCount=3, encoding="utf-8", ) _file_handler.setLevel(logging.INFO) _file_handler.setFormatter(logging.Formatter( "%(asctime)s %(levelname)-8s %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", )) logging.getLogger().addHandler(_file_handler) except Exception as _log_err: logger.warning("Could not set up file logging at %s: %s", _LOG_DIR, _log_err) # ── Static-asset caching middleware ────────────────────────────────────────── # JS and CSS are served with ?v= by loader.js — safe to cache for 1 year. # Section HTML uses ?v= (always fresh) so no long-term cache there. @app.middleware("http") async def static_cache_headers(request: Request, call_next): response: Response = await call_next(request) path = request.url.path has_version = bool(request.query_params.get("v")) if has_version and (path.startswith("/static/js/") or path.endswith(".css")): response.headers["Cache-Control"] = "public, max-age=31536000, immutable" elif path.startswith("/static/sections/"): response.headers["Cache-Control"] = "no-store" elif path.startswith("/static/") and not has_version: response.headers["Cache-Control"] = "public, max-age=3600" return response # ── Routers ─────────────────────────────────────────────────────────────────── app.include_router(admin.router) app.include_router(settings.router) app.include_router(library.router) app.include_router(stt.router) app.include_router(sources.router) app.include_router(docker.router) app.include_router(tts.router) app.include_router(conversation.router) # ── Static files ────────────────────────────────────────────────────────────── app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static") # ── Dev runner ──────────────────────────────────────────────────────────────── if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=7890, log_level="info")