diff --git a/src/alpine_bits_python/alpine_bits_helpers.py b/src/alpine_bits_python/alpine_bits_helpers.py index 5bf677a..1325818 100644 --- a/src/alpine_bits_python/alpine_bits_helpers.py +++ b/src/alpine_bits_python/alpine_bits_helpers.py @@ -681,7 +681,7 @@ def create_xml_from_db(list: list[Tuple[Reservation, Customer]]): for reservation, customer in list: _LOGGER.info( - f"Creating XML for reservation {reservation.form_id} and customer {customer.given_name}" + f"Creating XML for reservation {reservation.unique_id} and customer {customer.given_name}" ) try: @@ -718,10 +718,7 @@ def create_xml_from_db(list: list[Tuple[Reservation, Customer]]): reservation.num_adults, children_ages ) - unique_id_string = reservation.form_id - - if len(unique_id_string) > 32: - unique_id_string = unique_id_string[:32] # Truncate to 32 characters + unique_id_string = reservation.unique_id # UniqueID unique_id = OtaResRetrieveRs.ReservationsList.HotelReservation.UniqueId( @@ -828,7 +825,7 @@ def create_xml_from_db(list: list[Tuple[Reservation, Customer]]): except Exception as e: _LOGGER.error( - f"Error creating XML for reservation {reservation.form_id} and customer {customer.given_name}: {e}" + f"Error creating XML for reservation {reservation.unique_id} and customer {customer.given_name}: {e}" ) retrieved_reservations = OtaResRetrieveRs.ReservationsList( diff --git a/src/alpine_bits_python/alpinebits_server.py b/src/alpine_bits_python/alpinebits_server.py index 75ccccc..2dfb159 100644 --- a/src/alpine_bits_python/alpinebits_server.py +++ b/src/alpine_bits_python/alpinebits_server.py @@ -26,7 +26,7 @@ from xsdata.formats.dataclass.serializers.config import SerializerConfig from abc import ABC, abstractmethod from xsdata_pydantic.bindings import XmlParser import logging -from .db import Reservation, Customer +from .db import AckedRequest, Reservation, Customer from sqlalchemy import select from sqlalchemy.orm import joinedload @@ -493,6 +493,8 @@ class ReadAction(AlpineBitsAction): # query all reservations for this hotel from the database, where start_date is greater than or equal to the given start_date + + stmt = ( select(Reservation, Customer) .join(Customer, Reservation.customer_id == Customer.id) @@ -500,6 +502,20 @@ class ReadAction(AlpineBitsAction): ) if start_date: stmt = stmt.filter(Reservation.start_date >= start_date) + else: + # remove reservations that have been acknowledged via client_id + if client_info.client_id: + subquery = ( + select(Reservation.id) + .join( + AckedRequest, + AckedRequest.unique_id == Reservation.unique_id, + ) + .filter(AckedRequest.client_id == client_info.client_id) + ) + stmt = stmt.filter(~Reservation.id.in_(subquery)) + + result = await dbsession.execute(stmt) reservation_customer_pairs: list[tuple[Reservation, Customer]] = ( diff --git a/src/alpine_bits_python/api.py b/src/alpine_bits_python/api.py index 98d7d25..2490f73 100644 --- a/src/alpine_bits_python/api.py +++ b/src/alpine_bits_python/api.py @@ -16,7 +16,7 @@ from .config_loader import load_config from fastapi.responses import HTMLResponse, PlainTextResponse, Response from .models import WixFormSubmission from datetime import datetime, date, timezone -from .auth import validate_api_key, validate_wix_signature, generate_api_key +from .auth import generate_unique_id, validate_api_key, validate_wix_signature, generate_api_key from .rate_limit import ( limiter, webhook_limiter, @@ -280,12 +280,16 @@ async def process_wix_form_submission(request: Request, data: Dict[str, Any], db ("utm_Term", "utm_term"), ("utm_Content", "utm_content"), ] - utm_comment_text = [] - for label, field in utm_fields: - val = data.get(f"field:{field}") or data.get(label) - if val: - utm_comment_text.append(f"{label}: {val}") - utm_comment = ",".join(utm_comment_text) if utm_comment_text else None + + # get submissionId and ensure max length 35. Generate one if not present + + unique_id = data.get("submissionId", generate_unique_id()) + + if len(unique_id) > 35: + # strip to first 35 chars + unique_id = unique_id[:35] + + # use database session @@ -309,19 +313,19 @@ async def process_wix_form_submission(request: Request, data: Dict[str, Any], db name_title=None, ) db.add(db_customer) - await db.commit() - await db.refresh(db_customer) + await db.flush() # This assigns db_customer.id without committing + #await db.refresh(db_customer) + db_reservation = DBReservation( customer_id=db_customer.id, - form_id=data.get("submissionId"), + unique_id=unique_id, start_date=date.fromisoformat(start_date) if start_date else None, end_date=date.fromisoformat(end_date) if end_date else None, num_adults=num_adults, num_children=num_children, children_ages=",".join(str(a) for a in children_ages), offer=offer, - utm_comment=utm_comment, created_at=datetime.now(timezone.utc), utm_source=data.get("field:utm_source"), utm_medium=data.get("field:utm_medium"), diff --git a/src/alpine_bits_python/auth.py b/src/alpine_bits_python/auth.py index 5a7632e..6cb20e0 100644 --- a/src/alpine_bits_python/auth.py +++ b/src/alpine_bits_python/auth.py @@ -30,6 +30,10 @@ if os.getenv("WIX_API_KEY"): if os.getenv("ADMIN_API_KEY"): API_KEYS["admin-key"] = os.getenv("ADMIN_API_KEY") +def generate_unique_id() -> str: + """Generate a unique ID with max length 35 characters""" + return secrets.token_urlsafe(26)[:35] # 26 bytes -> 35 chars in base64url + def generate_api_key() -> str: """Generate a secure API key""" diff --git a/src/alpine_bits_python/db.py b/src/alpine_bits_python/db.py index 8810aca..1cc790d 100644 --- a/src/alpine_bits_python/db.py +++ b/src/alpine_bits_python/db.py @@ -44,14 +44,13 @@ class Reservation(Base): __tablename__ = "reservations" id = Column(Integer, primary_key=True) customer_id = Column(Integer, ForeignKey("customers.id")) - form_id = Column(String, unique=True) + unique_id = Column(String(35), unique=True) # max length 35 start_date = Column(Date) end_date = Column(Date) num_adults = Column(Integer) num_children = Column(Integer) children_ages = Column(String) # comma-separated offer = Column(String) - utm_comment = Column(String) created_at = Column(DateTime) # Add all UTM fields and user comment for XML utm_source = Column(String) @@ -68,11 +67,11 @@ class Reservation(Base): customer = relationship("Customer", back_populates="reservations") -class HashedCustomer(Base): - __tablename__ = "hashed_customers" + +# Table for tracking acknowledged requests by client +class AckedRequest(Base): + __tablename__ = 'acked_requests' id = Column(Integer, primary_key=True) - customer_id = Column(Integer) - hashed_email = Column(String) - hashed_phone = Column(String) - hashed_name = Column(String) - redacted_at = Column(DateTime) + client_id = Column(String, index=True) + unique_id = Column(String, index=True) # Should match Reservation.form_id or another unique field + timestamp = Column(DateTime) diff --git a/src/alpine_bits_python/main.py b/src/alpine_bits_python/main.py index 2d51ef4..3112d20 100644 --- a/src/alpine_bits_python/main.py +++ b/src/alpine_bits_python/main.py @@ -256,7 +256,7 @@ def create_xml_from_db(customer: DBCustomer, reservation: DBReservation): # UniqueID unique_id = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.UniqueId( - type_value=ab.UniqueIdType2.VALUE_14, id=reservation.form_id + type_value=ab.UniqueIdType2.VALUE_14, id=reservation.unique_id ) # TimeSpan