release: v2.2.81 contacts visibility and telephony/date/deploy fixes

This commit is contained in:
Christian 2026-05-04 19:20:55 +02:00
parent 8ec9400b15
commit 25530c7c94
6 changed files with 72 additions and 33 deletions

29
RELEASE_NOTES_v2.2.81.md Normal file
View File

@ -0,0 +1,29 @@
# Release Notes v2.2.81
Dato: 2026-05-04
## Fixes
- Kontakter: Stabiliseret paginering i `/api/v1/contacts` ved at tilfoeje deterministisk tie-break (`ORDER BY ... , c.id`).
- Kontakter: Fjernet skrøbelig frontend query-key short-circuit i kontaktlisten, som kunne medfoere at listen ikke blev genindlaest korrekt efter afbrudte requests.
- Telefoni: Rettet datofilter i `/api/v1/telefoni/calls` saa `date_to` er inklusiv hele dagen.
- Telefoni: Validerer nu tydeligt `date_from`/`date_to` format (`YYYY-MM-DD`) med 422 ved ugyldig input.
- Deployment: `updateto.sh` bruger nu dynamiske containernavne baseret paa `STACK_NAME` i stedet for hardcoded `-prod`.
## Beroerte filer
- `app/contacts/backend/router_simple.py`
- `app/contacts/frontend/contacts.html`
- `app/modules/telefoni/backend/router.py`
- `updateto.sh`
- `VERSION`
## Drift
Hvis stacken koerer som `v2`, deploy med:
```bash
sudo -iu bmcadmin
cd /srv/podman/bmc_hub_v2
./updateto.sh v2.2.81
```

View File

@ -1 +1 @@
2.2.80 2.2.81

View File

@ -140,7 +140,7 @@ async def get_contacts(
{where_sql} {where_sql}
GROUP BY c.id, c.first_name, c.last_name, c.email, c.phone, c.mobile, GROUP BY c.id, c.first_name, c.last_name, c.email, c.phone, c.mobile,
c.title, c.department, c.user_company, c.is_active, c.created_at, c.updated_at c.title, c.department, c.user_company, c.is_active, c.created_at, c.updated_at
ORDER BY company_count DESC, c.last_name, c.first_name ORDER BY company_count DESC, c.last_name, c.first_name, c.id
LIMIT %s OFFSET %s LIMIT %s OFFSET %s
""" """
params.extend([limit, offset]) params.extend([limit, offset])

View File

@ -813,7 +813,6 @@ let searchQuery = '';
let totalContacts = 0; let totalContacts = 0;
let searchTimeout = null; let searchTimeout = null;
let currentRequestController = null; let currentRequestController = null;
let lastLoadedQueryKey = '';
let availableCompanies = []; let availableCompanies = [];
let selectedCompanyIds = new Set(); let selectedCompanyIds = new Set();
let currentContactsData = []; let currentContactsData = [];
@ -941,12 +940,6 @@ async function loadContacts() {
params.append('is_active', 'false'); params.append('is_active', 'false');
} }
const queryKey = `${currentPage}|${pageSize}|${searchQuery}|${currentFilter}`;
if (queryKey === lastLoadedQueryKey) {
return;
}
lastLoadedQueryKey = queryKey;
const response = await fetch(`/api/v1/contacts?${params}`, { signal: currentRequestController.signal }); const response = await fetch(`/api/v1/contacts?${params}`, { signal: currentRequestController.signal });
const data = await response.json(); const data = await response.json();

View File

@ -3,7 +3,7 @@ import logging
import base64 import base64
import ipaddress import ipaddress
import re import re
from datetime import datetime from datetime import datetime, timedelta
from typing import Optional from typing import Optional
from urllib.error import URLError, HTTPError from urllib.error import URLError, HTTPError
from urllib.request import Request as UrlRequest, urlopen from urllib.request import Request as UrlRequest, urlopen
@ -653,15 +653,29 @@ async def list_calls(
where = [] where = []
params = [] params = []
parsed_date_from = None
parsed_date_to = None
if date_from:
try:
parsed_date_from = datetime.strptime(date_from, "%Y-%m-%d")
except ValueError:
raise HTTPException(status_code=422, detail="Invalid date_from format, expected YYYY-MM-DD")
if date_to:
try:
# Make date_to inclusive for the whole selected day.
parsed_date_to = datetime.strptime(date_to, "%Y-%m-%d") + timedelta(days=1)
except ValueError:
raise HTTPException(status_code=422, detail="Invalid date_to format, expected YYYY-MM-DD")
if user_id is not None: if user_id is not None:
where.append("t.bruger_id = %s") where.append("t.bruger_id = %s")
params.append(user_id) params.append(user_id)
if date_from: if parsed_date_from is not None:
where.append("t.started_at >= %s") where.append("t.started_at >= %s")
params.append(date_from) params.append(parsed_date_from)
if date_to: if parsed_date_to is not None:
where.append("t.started_at <= %s") where.append("t.started_at < %s")
params.append(date_to) params.append(parsed_date_to)
if without_case: if without_case:
where.append("t.sag_id IS NULL") where.append("t.sag_id IS NULL")

39
updateto.sh Normal file → Executable file
View File

@ -17,6 +17,9 @@ if [ "${EUID:-$(id -u)}" -eq 0 ]; then
fi fi
PODMAN_COMPOSE_FILE="docker-compose.prod.yml" PODMAN_COMPOSE_FILE="docker-compose.prod.yml"
STACK_NAME="${STACK_NAME:-prod}"
POSTGRES_CONTAINER="bmc-hub-postgres-${STACK_NAME}"
API_CONTAINER="bmc-hub-api-${STACK_NAME}"
if [ -z "$VERSION" ]; then if [ -z "$VERSION" ]; then
echo "❌ Fejl: Ingen version angivet" echo "❌ Fejl: Ingen version angivet"
@ -103,7 +106,7 @@ fi
# Guard against host port conflicts before attempting startup # Guard against host port conflicts before attempting startup
POSTGRES_BIND_ADDR="${POSTGRES_BIND_ADDR:-127.0.0.1}" POSTGRES_BIND_ADDR="${POSTGRES_BIND_ADDR:-127.0.0.1}"
POSTGRES_PORT="${POSTGRES_PORT:-5432}" 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 if podman ps --format '{{.Names}} {{.Ports}}' | grep -E "${POSTGRES_BIND_ADDR}:${POSTGRES_PORT}->5432/tcp" | grep -v "$POSTGRES_CONTAINER" >/dev/null 2>&1; then
echo "❌ Fejl: Portkonflikt på ${POSTGRES_BIND_ADDR}:${POSTGRES_PORT} (Postgres host-port)" echo "❌ Fejl: Portkonflikt på ${POSTGRES_BIND_ADDR}:${POSTGRES_PORT} (Postgres host-port)"
echo " Sæt en ledig port i .env, fx POSTGRES_PORT=5433" echo " Sæt en ledig port i .env, fx POSTGRES_PORT=5433"
podman ps --format 'table {{.Names}}\t{{.Ports}}' podman ps --format 'table {{.Names}}\t{{.Ports}}'
@ -111,11 +114,11 @@ if podman ps --format '{{.Names}} {{.Ports}}' | grep -E "${POSTGRES_BIND_ADDR}:$
fi fi
# Also detect stopped containers reserving legacy port mappings in config (rootlessport conflicts) # Also detect stopped containers reserving legacy port mappings in config (rootlessport conflicts)
if podman ps -a --format '{{.Names}} {{.Ports}}' | grep -E "${POSTGRES_BIND_ADDR}:${POSTGRES_PORT}->5432/tcp" | grep -v "bmc-hub-postgres-prod" >/dev/null 2>&1; then if podman ps -a --format '{{.Names}} {{.Ports}}' | grep -E "${POSTGRES_BIND_ADDR}:${POSTGRES_PORT}->5432/tcp" | grep -v "$POSTGRES_CONTAINER" >/dev/null 2>&1; then
echo "⚠️ Finder gamle containere med portbinding ${POSTGRES_BIND_ADDR}:${POSTGRES_PORT}; forsøger oprydning..." echo "⚠️ Finder gamle containere med portbinding ${POSTGRES_BIND_ADDR}:${POSTGRES_PORT}; forsøger oprydning..."
podman ps -a --format '{{.Names}} {{.Ports}}' \ podman ps -a --format '{{.Names}} {{.Ports}}' \
| grep -E "${POSTGRES_BIND_ADDR}:${POSTGRES_PORT}->5432/tcp" \ | grep -E "${POSTGRES_BIND_ADDR}:${POSTGRES_PORT}->5432/tcp" \
| grep -v "bmc-hub-postgres-prod" \ | grep -v "$POSTGRES_CONTAINER" \
| awk '{print $1}' \ | awk '{print $1}' \
| while read -r holder; do | while read -r holder; do
[ -z "$holder" ] && continue [ -z "$holder" ] && continue
@ -140,14 +143,14 @@ if ! podman-compose -f "$PODMAN_COMPOSE_FILE" build --no-cache && podman-compose
fi fi
# Validate that key containers are actually running after startup # Validate that key containers are actually running after startup
if ! podman ps --format '{{.Names}}' | grep -q '^bmc-hub-postgres-prod$'; then if ! podman ps --format '{{.Names}}' | grep -q "^${POSTGRES_CONTAINER}$"; then
echo "❌ Fejl: bmc-hub-postgres-prod kører ikke efter startup" echo "❌ Fejl: ${POSTGRES_CONTAINER} kører ikke efter startup"
podman ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' podman ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
exit 1 exit 1
fi fi
if ! podman ps --format '{{.Names}}' | grep -q '^bmc-hub-api-prod$'; then if ! podman ps --format '{{.Names}}' | grep -q "^${API_CONTAINER}$"; then
echo "❌ Fejl: bmc-hub-api-prod kører ikke efter startup" echo "❌ Fejl: ${API_CONTAINER} kører ikke efter startup"
podman ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' podman ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
exit 1 exit 1
fi fi
@ -165,7 +168,7 @@ done
if [ "$HEALTH_OK" != "true" ]; then if [ "$HEALTH_OK" != "true" ]; then
echo "❌ Fejl: API health fejlede på http://localhost:${API_PORT}/health" echo "❌ Fejl: API health fejlede på http://localhost:${API_PORT}/health"
podman logs --tail 120 bmc-hub-api-prod || true podman logs --tail 120 "$API_CONTAINER" || true
exit 1 exit 1
fi fi
@ -175,7 +178,7 @@ echo "📁 Syncer migrations fra container til host..."
SYNC_OK=false SYNC_OK=false
for i in {1..20}; do for i in {1..20}; do
rm -rf ./migrations_temp rm -rf ./migrations_temp
if podman cp bmc-hub-api-prod:/app/migrations ./migrations_temp 2>/dev/null; then if podman cp "$API_CONTAINER":/app/migrations ./migrations_temp 2>/dev/null; then
rm -rf ./migrations rm -rf ./migrations
mv ./migrations_temp ./migrations mv ./migrations_temp ./migrations
chmod -R 755 ./migrations chmod -R 755 ./migrations
@ -188,7 +191,7 @@ for i in {1..20}; do
done done
if [ "$SYNC_OK" != "true" ]; then if [ "$SYNC_OK" != "true" ]; then
echo "❌ Fejl: Kunne ikke sync'e migrations fra bmc-hub-api-prod:/app/migrations" echo "❌ Fejl: Kunne ikke sync'e migrations fra ${API_CONTAINER}:/app/migrations"
echo " Afbryder for at undgå kørsel af gamle migrations" echo " Afbryder for at undgå kørsel af gamle migrations"
exit 1 exit 1
fi fi
@ -216,19 +219,19 @@ if [ "${RUN_MIGRATIONS:-false}" = "true" ]; then
fi fi
for i in {1..30}; do for i in {1..30}; do
if podman exec bmc-hub-postgres-prod pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" >/dev/null 2>&1; then if podman exec "$POSTGRES_CONTAINER" pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" >/dev/null 2>&1; then
break break
fi fi
echo "⏳ Venter på postgres... ($i/30)" echo "⏳ Venter på postgres... ($i/30)"
sleep 2 sleep 2
done done
echo "📄 Kører alle migrations fra /docker-entrypoint-initdb.d (sorteret)..." echo "📄 Kører alle migrations fra /docker-entrypoint-initdb.d (sorteret)..."
podman exec bmc-hub-postgres-prod sh -lc "ls -1 /docker-entrypoint-initdb.d/*.sql 2>/dev/null | sort" \ podman exec "$POSTGRES_CONTAINER" sh -lc "ls -1 /docker-entrypoint-initdb.d/*.sql 2>/dev/null | sort" \
| while read -r file; do | while read -r file; do
[ -z "$file" ] && continue [ -z "$file" ] && continue
echo "➡️ $file" echo "➡️ $file"
podman exec -i bmc-hub-postgres-prod psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f "$file" podman exec -i "$POSTGRES_CONTAINER" psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f "$file"
done done
echo "✅ Migrations kørt" echo "✅ Migrations kørt"
@ -240,14 +243,14 @@ fi
echo "" echo ""
echo "📋 Logs fra startup:" echo "📋 Logs fra startup:"
echo "================================" echo "================================"
podman logs --tail 50 bmc-hub-api-prod podman logs --tail 50 "$API_CONTAINER"
echo "" echo ""
echo "✅ Deployment fuldført!" echo "✅ Deployment fuldført!"
echo "" echo ""
echo "🔍 Tjek status med:" echo "🔍 Tjek status med:"
echo " podman-compose -f $PODMAN_COMPOSE_FILE ps" echo " podman-compose -f $PODMAN_COMPOSE_FILE ps"
echo " podman logs -f bmc-hub-api-prod" echo " podman logs -f ${API_CONTAINER}"
echo "" echo ""
echo "🌐 Test health endpoint:" echo "🌐 Test health endpoint:"
echo " curl http://localhost:${API_PORT}/health" echo " curl http://localhost:${API_PORT}/health"