"""WSGI / ASGI application entrypoint. Gunicorn target stays app:asgi_app. Routes, caching, validation, and Redis statistics live in the adlist package. """ import hashlib from pathlib import Path from flask import Flask from flask_compress import Compress from flask_limiter import Limiter from asgiref.wsgi import WsgiToAsgi import config from adlist import services, pages, converter, metrics app = Flask(__name__) app.config.update(MAX_CONTENT_LENGTH=config.MAX_CONTENT_LENGTH, SECRET_KEY=config.SECRET_KEY) app.debug = config.FLASK_DEBUG limiter = Limiter( key_func=services.get_client_ip, app=app, default_limits=[config.RATE_LIMIT_DEFAULT], storage_uri=config.REDIS_URL, ) Compress(app) app.before_request(services.track_request_data) app.after_request(services.finalize_response) app.add_template_filter(services.datetimeformat_filter, "datetimeformat") # Fingerprinting keeps long-lived cache safe for upgraded CSS / JS assets. def _asset_revision(): root = Path(__file__).resolve().parent / "static" digest = hashlib.sha256() for asset in sorted(root.rglob("*.css")) + sorted(root.rglob("*.js")): digest.update(asset.relative_to(root).as_posix().encode("utf-8")) digest.update(asset.read_bytes()) return digest.hexdigest()[:12] ASSET_REVISION = _asset_revision() @app.context_processor def inject_common(): return {"asset_revision": ASSET_REVISION} app.add_url_rule("/", "index", pages.index, methods=["GET"]) app.add_url_rule("/favicon.ico", "favicon", pages.favicon, methods=["GET"]) app.add_url_rule("/convert", "convert", limiter.limit(config.RATE_LIMIT_CONVERT)(converter.convert), methods=["GET", "HEAD"]) for name, func in (("stats", metrics.stats), ("stats_json", metrics.stats_json)): wrapped = services.basic_auth_required( realm=config.STATS_BASIC_AUTH_REALM, user=config.STATS_BASIC_AUTH_USER, password=config.STATS_BASIC_AUTH_PASS, )(func) app.add_url_rule("/" + ("stats" if name == "stats" else "stats.json"), name, wrapped, methods=["GET"]) for code in (400, 403, 404, 413, 415, 422, 429, 500, 502): app.register_error_handler(code, pages.handle_errors) # Compatibility: integrations refer to app:app or app:asgi_app. asgi_app = WsgiToAsgi(app) convert = converter.convert stats = metrics.stats stats_json = metrics.stats_json redis_client = services.redis_client cache_headers = converter.cache_headers if __name__ == "__main__": app.run(host=config.BIND_HOST, port=config.BIND_PORT)