diff options
Diffstat (limited to 'server.py')
| -rw-r--r-- | server.py | 67 |
1 files changed, 55 insertions, 12 deletions
@@ -3,12 +3,15 @@ python3 server.py # http://localhost:8000 python3 server.py 8080 # ポート指定 HOST=0.0.0.0 python3 server.py # コンテナ内など外から受ける場合 + +リンキィディンクの取得には RINKY_ID / RINKY_PASSWORD が要る(.env に書けば読み込む)。 """ import json import os import sys -from datetime import date +from concurrent.futures import ThreadPoolExecutor +from datetime import date, timedelta from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from urllib.parse import parse_qs, urlparse @@ -17,22 +20,53 @@ import providers ROOT = Path(__file__).resolve().parent STATIC = ROOT / "static" +MAX_DAYS_AHEAD = 92 # リンキィディンクは 3 ヶ月先まで予約可 + + +def load_env(): + env = ROOT / ".env" + if not env.exists(): + return + for line in env.read_text(encoding="utf-8").splitlines(): + if "=" in line and not line.lstrip().startswith("#"): + key, value = line.split("=", 1) + os.environ.setdefault(key.strip(), value.strip()) -def load_studios(): +def load_config(): return json.loads((ROOT / "studios.json").read_text(encoding="utf-8")) -def slots_for(day): - result = [] - for studio in load_studios(): +def studio_info(studio_id, studio): + return { + "id": studio_id, + "name": studio["name"], + "area": studio["area"], + "url": studio["url"], + "phone": studio["phone"], + "booking": providers.booking_url(studio), + "online": bool(studio.get("provider")), + } + + +def slots_for(set_id, day): + config = load_config() + target = next((s for s in config["sets"] if s["id"] == set_id), None) + if not target: + return None + + def one(studio_id): + studio = config["studios"][studio_id] + info = studio_info(studio_id, studio) try: - slots = providers.fetch(studio, day) - error = None + rooms, fetched_at = providers.fetch(studio_id, studio, day) + return {**info, "rooms": rooms, "fetched_at": fetched_at, "error": None} except Exception as e: # 1軒の取得失敗で一覧全体を落とさない - slots, error = [], str(e) - result.append({**studio, "slots": slots, "error": error}) - return result + return {**info, "rooms": None, "fetched_at": None, "error": str(e)} + + with ThreadPoolExecutor(max_workers=4) as pool: + studios = list(pool.map(one, target["studios"])) + return {"set": target["id"], "date": day.isoformat(), "studios": studios} class Handler(SimpleHTTPRequestHandler): @@ -41,14 +75,22 @@ class Handler(SimpleHTTPRequestHandler): def do_GET(self): url = urlparse(self.path) + qs = parse_qs(url.query) + if url.path == "/api/sets": + config = load_config() + return self.send_json([{"id": s["id"], "name": s["name"]} for s in config["sets"]]) if url.path != "/api/slots": return super().do_GET() - qs = parse_qs(url.query) try: day = date.fromisoformat(qs.get("date", [date.today().isoformat()])[0]) except ValueError: return self.send_json({"error": "date は YYYY-MM-DD で指定してください"}, 400) - self.send_json({"date": day.isoformat(), "studios": slots_for(day)}) + if not date.today() - timedelta(days=1) <= day <= date.today() + timedelta(days=MAX_DAYS_AHEAD): + return self.send_json({"error": f"date は今日から {MAX_DAYS_AHEAD} 日先までです"}, 400) + body = slots_for(qs.get("set", [""])[0], day) + if body is None: + return self.send_json({"error": "set が見つかりません"}, 404) + self.send_json(body) def send_json(self, body, status=200): data = json.dumps(body, ensure_ascii=False).encode("utf-8") @@ -60,6 +102,7 @@ class Handler(SimpleHTTPRequestHandler): if __name__ == "__main__": + load_env() port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000 host = os.environ.get("HOST", "127.0.0.1") print(f"http://{host}:{port}", flush=True) |
