#!/usr/bin/env python3 """ Combined HTTP server: serves static files from DATA_DIR and provides the ratings API at /api/ratings. GET /api/ratings → returns ratings.json contents POST /api/ratings → saves entire ratings object GET /api/ratings/export → same as GET, with Content-Disposition: attachment GET / → serves static file from DATA_DIR """ import argparse import json import logging import os import sys from pathlib import Path from flask import Flask, jsonify, request, send_from_directory parser = argparse.ArgumentParser(description="Flat-search map server") parser.add_argument("--log-level", "-l", default=None, choices=["DEBUG", "INFO", "WARNING", "ERROR"], help="Log level (default: INFO)") parser.add_argument("--verbose", "-v", action="store_true", help="Shorthand for --log-level DEBUG") args = parser.parse_args() log_level = logging.DEBUG if args.verbose else getattr(logging, args.log_level or "INFO") PORT = int(os.environ.get("PORT", 8080)) DATA_DIR = Path(os.environ.get("DATA_DIR", ".")).resolve() RATINGS_FILE = DATA_DIR / "ratings.json" logging.basicConfig( level=log_level, format="%(asctime)s [server] %(levelname)s %(message)s", datefmt="%Y-%m-%dT%H:%M:%S", ) log = logging.getLogger(__name__) app = Flask(__name__, static_folder=None) app.json.ensure_ascii = False @app.after_request def add_cors(response): response.headers["Access-Control-Allow-Origin"] = "*" response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS" response.headers["Access-Control-Allow-Headers"] = "Content-Type" return response def load_ratings() -> dict: try: if RATINGS_FILE.exists(): return json.loads(RATINGS_FILE.read_text(encoding="utf-8")) except Exception as e: log.error("Failed to load ratings: %s", e) return {} def save_ratings(data: dict) -> None: RATINGS_FILE.write_text( json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8", ) @app.route("/api/ratings", methods=["OPTIONS"]) @app.route("/api/ratings/export", methods=["OPTIONS"]) def ratings_options(): return ("", 204) @app.route("/api/ratings", methods=["GET"]) def get_ratings(): ratings = load_ratings() log.info("GET /api/ratings → %d ratings", len(ratings)) return jsonify(ratings) @app.route("/api/ratings/export", methods=["GET"]) def export_ratings(): ratings = load_ratings() log.info("GET /api/ratings/export → %d ratings", len(ratings)) response = jsonify(ratings) response.headers["Content-Disposition"] = 'attachment; filename="ratings.json"' return response @app.route("/api/ratings", methods=["POST"]) def post_ratings(): length = request.content_length if not length: return jsonify({"error": "empty body"}), 400 try: data = request.get_json(force=True, silent=False) except Exception as e: log.warning("Bad request body: %s", e) return jsonify({"error": "invalid JSON"}), 400 if not isinstance(data, dict): return jsonify({"error": "expected JSON object"}), 400 save_ratings(data) log.info("POST /api/ratings → saved %d ratings", len(data)) return jsonify({"ok": True, "count": len(data)}) @app.route("/") def index(): return send_from_directory(str(DATA_DIR), "mapa_bytu.html") @app.route("/") def static_files(filename): return send_from_directory(str(DATA_DIR), filename) if __name__ == "__main__": log.info("Server starting on port %d, data dir: %s", PORT, DATA_DIR) log.info("Ratings file: %s", RATINGS_FILE) app.run(host="0.0.0.0", port=PORT)