Hotfix: Yealink GET webhooks + safer podman deploy checks (v2.2.42)

This commit is contained in:
Christian 2026-03-03 23:09:14 +01:00
parent 91f709f4fe
commit 45d8f4209b
4 changed files with 96 additions and 2 deletions

18
RELEASE_NOTES_v2.2.42.md Normal file
View File

@ -0,0 +1,18 @@
# Release Notes v2.2.42
Dato: 3. marts 2026
## Fix: Yealink webhook compatibility + deploy robusthed
- Tilføjet `GET` support på Mission Control telefoni-webhooks, så Yealink callback-URLs ikke returnerer `405 Method Not Allowed`.
- Webhook-endpoints understøtter nu query-parametre for `call_id`, `caller_number`, `queue_name` og valgfri `timestamp`.
- `updateto.sh` er hærdet med tydelig fail-fast ved portkonflikter og mislykket container-opstart, så scriptet ikke melder succes ved delvis fejl.
## Ændrede filer
- `app/dashboard/backend/mission_router.py`
- `updateto.sh`
- `VERSION`
## Påvirkede endpoints
- `/api/v1/mission/webhook/telefoni/ringing` (`POST` + `GET`)
- `/api/v1/mission/webhook/telefoni/answered` (`POST` + `GET`)
- `/api/v1/mission/webhook/telefoni/hangup` (`POST` + `GET`)

View File

@ -1 +1 @@
2.2.41
2.2.42

View File

@ -32,6 +32,37 @@ class MissionUptimeWebhook(BaseModel):
payload: Dict[str, Any] = Field(default_factory=dict)
def _first_query_param(request: Request, *names: str) -> Optional[str]:
for name in names:
value = request.query_params.get(name)
if value and str(value).strip():
return str(value).strip()
return None
def _parse_query_timestamp(request: Request) -> Optional[datetime]:
raw = _first_query_param(request, "timestamp", "time", "event_time")
if not raw:
return None
try:
return datetime.fromisoformat(raw.replace("Z", "+00:00"))
except Exception:
return None
def _event_from_query(request: Request) -> MissionCallEvent:
call_id = _first_query_param(request, "call_id", "callid", "id", "session_id", "uuid")
if not call_id:
raise HTTPException(status_code=400, detail="Missing call_id query parameter")
return MissionCallEvent(
call_id=call_id,
caller_number=_first_query_param(request, "caller_number", "caller", "from", "number", "phone"),
queue_name=_first_query_param(request, "queue_name", "queue", "group", "line"),
timestamp=_parse_query_timestamp(request),
)
def _get_webhook_token() -> str:
db_token = MissionService.get_setting_value("mission_webhook_token", "") or ""
env_token = (getattr(settings, "MISSION_WEBHOOK_TOKEN", "") or "").strip()
@ -175,6 +206,12 @@ async def mission_telefoni_ringing(event: MissionCallEvent, request: Request, to
return {"status": "ok"}
@router.get("/mission/webhook/telefoni/ringing")
async def mission_telefoni_ringing_get(request: Request, token: Optional[str] = Query(None)):
event = _event_from_query(request)
return await mission_telefoni_ringing(event, request, token)
@router.post("/mission/webhook/telefoni/answered")
async def mission_telefoni_answered(event: MissionCallEvent, request: Request, token: Optional[str] = Query(None)):
_validate_mission_webhook_token(request, token)
@ -204,6 +241,12 @@ async def mission_telefoni_answered(event: MissionCallEvent, request: Request, t
return {"status": "ok"}
@router.get("/mission/webhook/telefoni/answered")
async def mission_telefoni_answered_get(request: Request, token: Optional[str] = Query(None)):
event = _event_from_query(request)
return await mission_telefoni_answered(event, request, token)
@router.post("/mission/webhook/telefoni/hangup")
async def mission_telefoni_hangup(event: MissionCallEvent, request: Request, token: Optional[str] = Query(None)):
_validate_mission_webhook_token(request, token)
@ -233,6 +276,12 @@ async def mission_telefoni_hangup(event: MissionCallEvent, request: Request, tok
return {"status": "ok"}
@router.get("/mission/webhook/telefoni/hangup")
async def mission_telefoni_hangup_get(request: Request, token: Optional[str] = Query(None)):
event = _event_from_query(request)
return await mission_telefoni_hangup(event, request, token)
@router.post("/mission/webhook/uptime")
async def mission_uptime_webhook(payload: MissionUptimeWebhook, request: Request, token: Optional[str] = Query(None)):
_validate_mission_webhook_token(request, token)

View File

@ -74,6 +74,16 @@ fi
echo "✅ .env opdateret"
# Guard against host port conflicts before attempting startup
POSTGRES_BIND_ADDR="${POSTGRES_BIND_ADDR:-127.0.0.1}"
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
if podman ps --format '{{.Names}} {{.Ports}}' | grep -E "${POSTGRES_BIND_ADDR}:${POSTGRES_PORT}->5432/tcp" | grep -v "bmc-hub-postgres-prod" >/dev/null 2>&1; then
echo "❌ Fejl: Portkonflikt på ${POSTGRES_BIND_ADDR}:${POSTGRES_PORT} (Postgres host-port)"
echo " Sæt en ledig port i .env, fx POSTGRES_PORT=5433"
podman ps --format 'table {{.Names}}\t{{.Ports}}'
exit 1
fi
# Stop containers
echo ""
echo "⏹️ Stopper containere..."
@ -82,7 +92,24 @@ podman-compose -f "$PODMAN_COMPOSE_FILE" down
# Pull/rebuild with new version
echo ""
echo "🔨 Bygger nyt image med version $VERSION..."
podman-compose -f "$PODMAN_COMPOSE_FILE" up -d --build
if ! podman-compose -f "$PODMAN_COMPOSE_FILE" up -d --build; then
echo "❌ Fejl: podman-compose up fejlede"
echo " Tjek logs med: podman-compose -f $PODMAN_COMPOSE_FILE logs --tail=200"
exit 1
fi
# Validate that key containers are actually running after startup
if ! podman ps --format '{{.Names}}' | grep -q '^bmc-hub-postgres-prod$'; then
echo "❌ Fejl: bmc-hub-postgres-prod kører ikke efter startup"
podman ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
exit 1
fi
if ! podman ps --format '{{.Names}}' | grep -q '^bmc-hub-api-prod$'; then
echo "❌ Fejl: bmc-hub-api-prod kører ikke efter startup"
podman ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
exit 1
fi
# Sync migrations from container to host
echo ""