""" Lightweight Flask server for the meal order form. Serves the generated HTML form and handles order submissions. Orders are saved as JSON files in the orders directory, one per employee per week. """ import json import urllib.request from datetime import datetime from decimal import Decimal, ROUND_HALF_UP from pathlib import Path import boto3 from flask import Flask, jsonify, request, send_file PROJECT_ROOT = Path(__file__).resolve().parents[2] CONFIG_PATH = PROJECT_ROOT / "config.json" OUTPUT_DIR = PROJECT_ROOT / "output" ORDERS_DIR = PROJECT_ROOT / "orders" app = Flask(__name__) def load_config(): with open(CONFIG_PATH) as f: return json.load(f) def current_week() -> str: return datetime.now().strftime("%Y-W%U") def latest_menu_file() -> Path | None: files = sorted(OUTPUT_DIR.glob("menu-*.json"), reverse=True) return files[0] if files else None @app.route("/") def index(): form_file = OUTPUT_DIR / f"order-form-{current_week()}.html" if not form_file.exists(): return "No order form generated for this week. Run generate_form.py first.", 404 return send_file(form_file) @app.route("/api/menu") def get_menu(): menu_file = latest_menu_file() if not menu_file: return jsonify( {"error": "No menu data available. Run scrape_menu.py first."} ), 404 with open(menu_file) as f: return jsonify(json.load(f)) @app.route("/api/roster") def get_roster(): config = load_config() return jsonify(config.get("roster", [])) _google_client_id_cache = None def _get_google_client_id() -> str: global _google_client_id_cache if _google_client_id_cache is None: config = load_config() _google_client_id_cache = config.get("google_client_id", "") if not _google_client_id_cache: try: ssm = boto3.client("ssm") resp = ssm.get_parameter(Name="/meal-order-manager/google-client-id") _google_client_id_cache = resp["Parameter"]["Value"] except Exception: _google_client_id_cache = "" return _google_client_id_cache def _verify_google_token(token: str, client_id: str) -> dict | None: if not client_id: return None try: req = urllib.request.Request( f"https://oauth2.googleapis.com/tokeninfo?id_token={token}" ) with urllib.request.urlopen(req, timeout=5) as resp: data = json.loads(resp.read()) if data.get("aud") != client_id: return None return {"name": data.get("name", ""), "email": data.get("email", "")} except Exception: return None @app.route("/api/submit-order", methods=["POST"]) def submit_order(): data = request.get_json() if not data: return jsonify({"error": "No data received"}), 400 google_token = data.get("google_id_token") if google_token: client_id = _get_google_client_id() user_info = _verify_google_token(google_token, client_id) if not user_info: return jsonify({"error": "Invalid or unauthorized Google account"}), 403 name = user_info["name"] email = user_info["email"] else: name = data.get("employee_name", "").strip() email = data.get("employee_email", "").strip() items = data.get("items", []) if not name: return jsonify({"error": "Employee name is required"}), 400 if not email: return jsonify({"error": "Employee email is required"}), 400 if not items or not any(i.get("quantity", 0) > 0 for i in items): return jsonify({"error": "Please select at least one meal"}), 400 config = load_config() TWO_PLACES = Decimal("0.01") bulk_pct = Decimal(str(config.get("bulk_discount_percent", 0))) subsidy_pct = Decimal(str(config.get("company_subsidy_percent", 0))) bulk_mult = Decimal("1") - (bulk_pct / Decimal("100")) subsidy_mult = Decimal("1") - (subsidy_pct / Decimal("100")) filtered = [i for i in items if i.get("quantity", 0) > 0] for item in filtered: retail = Decimal(str(item.get("retail_price", item.get("price", 0)) or 0)) qty = Decimal(str(item.get("quantity", 0))) bulk_price = (retail * bulk_mult).quantize(TWO_PLACES, rounding=ROUND_HALF_UP) emp_price = (bulk_price * subsidy_mult).quantize( TWO_PLACES, rounding=ROUND_HALF_UP ) subtotal = (emp_price * qty).quantize(TWO_PLACES, rounding=ROUND_HALF_UP) item["retail_price"] = float(retail) item["bulk_price"] = float(bulk_price) item["price"] = float(emp_price) item["subtotal"] = float(subtotal) week = current_week() week_dir = ORDERS_DIR / week week_dir.mkdir(parents=True, exist_ok=True) safe_name = ( "".join(c if c.isalnum() or c in "-_ " else "" for c in name) .strip() .replace(" ", "-") .lower() ) order_file = week_dir / f"{safe_name}.json" total = float( sum(Decimal(str(i["subtotal"])) for i in filtered).quantize( TWO_PLACES, rounding=ROUND_HALF_UP ) ) order = { "employee_name": name, "employee_email": email, "week": week, "submitted_at": datetime.now().isoformat(), "items": filtered, "total": total, } with open(order_file, "w") as f: json.dump(order, f, indent=2) return jsonify( {"status": "ok", "message": f"Order saved for {name}", "total": order["total"]} ) @app.route("/api/form-status/") def form_status(week: str): return jsonify({"week": week, "status": "open"}) @app.route("/api/orders/") def get_orders(week: str): week_dir = ORDERS_DIR / week if not week_dir.exists(): return jsonify({"orders": [], "week": week}) orders = [] for f in sorted(week_dir.glob("*.json")): with open(f) as fh: orders.append(json.load(fh)) return jsonify({"orders": orders, "week": week}) if __name__ == "__main__": ORDERS_DIR.mkdir(exist_ok=True) print(f"Menu file: {latest_menu_file()}") print(f"Orders dir: {ORDERS_DIR}") app.run(host="0.0.0.0", port=5050, debug=True)