diff --git a/src/alpine_bits_python/api.py b/src/alpine_bits_python/api.py
index 4e2844c..3feb831 100644
--- a/src/alpine_bits_python/api.py
+++ b/src/alpine_bits_python/api.py
@@ -26,6 +26,7 @@ from fastapi.security import (
)
from pydantic import BaseModel
from slowapi.errors import RateLimitExceeded
+from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from alpine_bits_python.schemas import ReservationData
@@ -73,10 +74,7 @@ TOKEN_LOG_LENGTH = 10
def get_advertising_account_ids(
- config: dict[str, Any],
- hotel_code: str,
- fbclid: str | None,
- gclid: str | None
+ config: dict[str, Any], hotel_code: str, fbclid: str | None, gclid: str | None
) -> tuple[str | None, str | None]:
"""Get advertising account IDs based on hotel config and click IDs.
@@ -830,13 +828,22 @@ async def process_generic_webhook_submission(
_LOGGER.warning("Invalid age value for child %d: %s", i, age_str)
# Extract tracking information
- utm_source = tracking_data.get("utm_source")
- utm_medium = tracking_data.get("utm_medium")
- utm_campaign = tracking_data.get("utm_campaign")
- utm_term = tracking_data.get("utm_term")
- utm_content = tracking_data.get("utm_content")
- fbclid = tracking_data.get("fbclid")
- gclid = tracking_data.get("gclid")
+ utm_source = None
+ utm_medium = None
+ utm_campaign = None
+ utm_term = None
+ utm_content = None
+ fbclid = None
+ gclid = None
+
+ if tracking_data:
+ utm_source = tracking_data.get("utm_source")
+ utm_medium = tracking_data.get("utm_medium")
+ utm_campaign = tracking_data.get("utm_campaign")
+ utm_term = tracking_data.get("utm_term")
+ utm_content = tracking_data.get("utm_content")
+ fbclid = tracking_data.get("fbclid")
+ gclid = tracking_data.get("gclid")
# Parse submission timestamp
submission_time = data.get("timestamp")
@@ -1009,11 +1016,21 @@ async def handle_wix_form(
"""
try:
return await process_wix_form_submission(request, data, db_session)
+ except IntegrityError as e:
+ # Handle duplicate submissions gracefully - likely same form sent twice
+ # or race condition between workers
+ if "unique constraint" in str(e).lower() and "unique_id" in str(e).lower():
+ _LOGGER.warning(
+ "Duplicate submission detected (unique_id already exists). "
+ "Returning success to prevent retry. Error: %s",
+ str(e),
+ )
+ # Return success since the reservation already exists
+ return {"status": "success", "message": "Reservation already processed"}
+ # Re-raise if it's a different integrity error
+ raise
except Exception as e:
- _LOGGER.exception("Error in handle_wix_form: %s", e)
-
- # Log error data to file asynchronously
- import traceback
+ _LOGGER.exception("Error in handle_wix_form")
log_entry = {
"timestamp": datetime.now().isoformat(),
@@ -1021,7 +1038,6 @@ async def handle_wix_form(
"headers": dict(request.headers),
"data": data,
"error": str(e),
- "traceback": traceback.format_exc(),
}
# Use asyncio to run file I/O in thread pool to avoid blocking
@@ -1298,14 +1314,14 @@ async def handle_xml_upload(
success
Conversion data processed successfully
- {processing_stats['total_reservations']}
- {processing_stats['deleted_reservations']}
- {processing_stats['total_daily_sales']}
- {processing_stats['matched_to_reservation']}
- {processing_stats['matched_to_customer']}
- {processing_stats['matched_to_hashed_customer']}
- {processing_stats['unmatched']}
- {processing_stats['errors']}
+ {processing_stats["total_reservations"]}
+ {processing_stats["deleted_reservations"]}
+ {processing_stats["total_daily_sales"]}
+ {processing_stats["matched_to_reservation"]}
+ {processing_stats["matched_to_customer"]}
+ {processing_stats["matched_to_hashed_customer"]}
+ {processing_stats["unmatched"]}
+ {processing_stats["errors"]}
"""