mirror of
https://github.com/Sea-Haven-Industries/meal-order-manager.git
synced 2026-09-30 20:33:13 +00:00
Submit order Lambda now verifies Google ID tokens via tokeninfo endpoint, calculates two-tier discount pricing server-side, and async-invokes the Slack notifier for per-employee order confirmation DMs. Deadlines updated to Thursday 11:59pm across all Slack messages.
195 lines
5.7 KiB
Python
195 lines
5.7 KiB
Python
import json
|
|
import os
|
|
import urllib.request
|
|
from datetime import datetime
|
|
from zoneinfo import ZoneInfo
|
|
|
|
import boto3
|
|
|
|
from shared.db import current_week, get_form_status, get_roster, get_settings, put_order
|
|
from shared.secrets import get_parameter, get_secret
|
|
|
|
EASTERN = ZoneInfo("America/New_York")
|
|
_api_key = None
|
|
_settings = None
|
|
_google_client_id = None
|
|
_lambda = boto3.client("lambda")
|
|
|
|
|
|
def _get_api_key() -> str:
|
|
global _api_key
|
|
if _api_key is None:
|
|
_api_key = get_secret(os.environ["FORM_API_KEY_SECRET"])
|
|
return _api_key
|
|
|
|
|
|
def _get_discount_settings() -> tuple[float, float]:
|
|
global _settings
|
|
if _settings is None:
|
|
s = get_settings()
|
|
_settings = (
|
|
float(s.get("bulk_discount_percent", 0)),
|
|
float(s.get("company_subsidy_percent", 0)),
|
|
)
|
|
return _settings
|
|
|
|
|
|
def _get_google_client_id() -> str:
|
|
global _google_client_id
|
|
if _google_client_id is None:
|
|
param = os.environ.get("GOOGLE_CLIENT_ID_PARAM", "")
|
|
if param:
|
|
_google_client_id = get_parameter(param, decrypt=False) or ""
|
|
else:
|
|
_google_client_id = ""
|
|
return _google_client_id
|
|
|
|
|
|
def _verify_google_token(token: str) -> dict | None:
|
|
client_id = _get_google_client_id()
|
|
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
|
|
if data.get("hd") != "seahavenind.com":
|
|
return None
|
|
return {"name": data.get("name", ""), "email": data.get("email", "")}
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def lambda_handler(event, context):
|
|
method = event.get("requestContext", {}).get("http", {}).get("method", "GET")
|
|
path = event.get("rawPath", "")
|
|
|
|
if "/form-status/" in path:
|
|
return handle_form_status(event)
|
|
|
|
if "/roster" in path:
|
|
return handle_roster()
|
|
|
|
if method == "POST":
|
|
return handle_submit(event)
|
|
|
|
return response(405, {"error": "Method not allowed"})
|
|
|
|
|
|
def handle_form_status(event):
|
|
week = event.get("pathParameters", {}).get("week", current_week())
|
|
status = get_form_status(week)
|
|
return response(200, {"week": week, "status": status})
|
|
|
|
|
|
def handle_roster():
|
|
roster = get_roster()
|
|
employees = [{"name": e["name"], "email": e["email"]} for e in roster]
|
|
return response(200, {"employees": employees})
|
|
|
|
|
|
def handle_submit(event):
|
|
api_key = event.get("headers", {}).get("x-api-key", "")
|
|
if api_key != _get_api_key():
|
|
return response(403, {"error": "Invalid API key"})
|
|
|
|
try:
|
|
body = json.loads(event.get("body", "{}"))
|
|
except json.JSONDecodeError:
|
|
return response(400, {"error": "Invalid JSON"})
|
|
|
|
google_token = body.get("google_id_token")
|
|
if google_token:
|
|
user_info = _verify_google_token(google_token)
|
|
if not user_info:
|
|
return response(403, {"error": "Invalid or unauthorized Google account"})
|
|
name = user_info["name"]
|
|
email = user_info["email"]
|
|
else:
|
|
name = body.get("employee_name", "").strip()
|
|
email = body.get("employee_email", "").strip()
|
|
|
|
items = body.get("items", [])
|
|
|
|
if not name:
|
|
return response(400, {"error": "Employee name is required"})
|
|
if not email:
|
|
return response(400, {"error": "Employee email is required"})
|
|
if not items or not any(i.get("quantity", 0) > 0 for i in items):
|
|
return response(400, {"error": "Please select at least one meal"})
|
|
|
|
week = current_week()
|
|
status = get_form_status(week)
|
|
if status == "closed":
|
|
return response(410, {"error": "Orders are closed for this week"})
|
|
if status == "not_found":
|
|
return response(404, {"error": "No menu available for this week"})
|
|
|
|
filtered_items = [i for i in items if i.get("quantity", 0) > 0]
|
|
|
|
bulk_pct, subsidy_pct = _get_discount_settings()
|
|
bulk_mult = 1 - (bulk_pct / 100)
|
|
subsidy_mult = 1 - (subsidy_pct / 100)
|
|
|
|
for item in filtered_items:
|
|
retail = item.get("retail_price", item.get("price", 0)) or 0
|
|
qty = item.get("quantity", 0)
|
|
bulk_price = round(retail * bulk_mult, 2)
|
|
emp_price = round(bulk_price * subsidy_mult, 2)
|
|
item["retail_price"] = retail
|
|
item["bulk_price"] = bulk_price
|
|
item["price"] = emp_price
|
|
item["subtotal"] = round(emp_price * qty, 2)
|
|
|
|
total = sum(i["subtotal"] for i in filtered_items)
|
|
|
|
slug = (
|
|
"".join(c if c.isalnum() or c in "- " else "" for c in name)
|
|
.strip()
|
|
.replace(" ", "-")
|
|
.lower()
|
|
)
|
|
|
|
order_data = {
|
|
"employee_name": name,
|
|
"employee_email": email,
|
|
"submitted_at": datetime.now(EASTERN).isoformat(),
|
|
"items": filtered_items,
|
|
"total": round(total, 2),
|
|
}
|
|
|
|
put_order(week, slug, order_data)
|
|
|
|
_lambda.invoke(
|
|
FunctionName=os.environ["SLACK_NOTIFIER_ARN"],
|
|
InvocationType="Event",
|
|
Payload=json.dumps({
|
|
"event": "order_confirmed",
|
|
"employee_name": name,
|
|
"employee_email": email,
|
|
"items": filtered_items,
|
|
"total": order_data["total"],
|
|
"week": week,
|
|
}, default=float),
|
|
)
|
|
|
|
return response(
|
|
200,
|
|
{
|
|
"status": "ok",
|
|
"message": f"Order saved for {name}",
|
|
"total": order_data["total"],
|
|
},
|
|
)
|
|
|
|
|
|
def response(status_code: int, body: dict) -> dict:
|
|
return {
|
|
"statusCode": status_code,
|
|
"headers": {"Content-Type": "application/json"},
|
|
"body": json.dumps(body),
|
|
}
|