mirror of
https://github.com/Sea-Haven-Industries/afterhours-shift-manager.git
synced 2026-09-30 05:33:12 +00:00
* feat(api): collapse Slack, portal, and jobs onto Fargate (PLAT-216) Move HTTP and scheduled work onto one always-on Flask task so after-hours loses Lambda cold start without changing the Cognito or roster contracts. * fix(portal-api): keep CORS headers on unexpected 500s Portal SPA error handling needs Access-Control-Allow-Origin even when DynamoDB or other internals fail, otherwise the browser hides the 500. * fix(api): retarget holidays per account and ship App Home changelog (PLAT-216) * fix(iam): list ECS tasks and fail closed on non-prod Paychex (PLAT-216) * fix(portal-api): serve portal JSON with an explicit JSON content type
79 lines
2.3 KiB
Python
79 lines
2.3 KiB
Python
"""In-process job dispatch. SQS when JOBS_QUEUE_URL is set; otherwise run inline."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import json
|
|
import logging
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import boto3
|
|
|
|
from shared.holiday_flow import activate, deactivate
|
|
from shared.schedule import ShiftSchedule
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_SRC = Path(__file__).resolve().parents[1]
|
|
_sqs = None
|
|
_handlers: dict[str, object] = {}
|
|
|
|
|
|
def _client():
|
|
global _sqs
|
|
if _sqs is None:
|
|
_sqs = boto3.client("sqs")
|
|
return _sqs
|
|
|
|
|
|
def _load_lambda_app(dirname: str):
|
|
if dirname in _handlers:
|
|
return _handlers[dirname]
|
|
path = _SRC / dirname / "app.py"
|
|
name = f"afterhours_{dirname.replace('-', '_')}"
|
|
spec = importlib.util.spec_from_file_location(name, path)
|
|
if spec is None or spec.loader is None:
|
|
raise ImportError(f"cannot load {path}")
|
|
mod = importlib.util.module_from_spec(spec)
|
|
sys.modules[name] = mod
|
|
spec.loader.exec_module(mod)
|
|
_handlers[dirname] = mod
|
|
return mod
|
|
|
|
|
|
def enqueue_job(payload: dict) -> None:
|
|
queue_url = os.environ.get("JOBS_QUEUE_URL", "").strip()
|
|
body = json.dumps(payload, default=str)
|
|
if not queue_url:
|
|
run_job(payload)
|
|
return
|
|
_client().send_message(QueueUrl=queue_url, MessageBody=body)
|
|
|
|
|
|
def _run_holiday(payload: dict) -> dict:
|
|
action = payload.get("action")
|
|
date = payload.get("date")
|
|
if not date:
|
|
return {"error": True, "reason": "missing_date"}
|
|
schedule = ShiftSchedule()
|
|
if action == "activate":
|
|
return activate(schedule, date)
|
|
if action == "deactivate":
|
|
return deactivate(schedule, date)
|
|
return {"error": True, "reason": "unknown_action", "action": action}
|
|
|
|
|
|
def run_job(payload: dict) -> dict:
|
|
event_type = payload.get("event", "")
|
|
if event_type == "holiday":
|
|
return _run_holiday(payload)
|
|
forced = {**payload, "force": True}
|
|
if event_type == "weekly_post":
|
|
return _load_lambda_app("weekly-post").handler(forced, None)
|
|
if event_type == "roster_sync":
|
|
return _load_lambda_app("roster-sync").handler(forced, None)
|
|
if event_type in {"ring_scheduler_daily", "ring_scheduler_weekend"}:
|
|
return _load_lambda_app("ring-scheduler").handler(forced, None)
|
|
raise ValueError(f"unknown job event {event_type!r}")
|