142 lines
5.1 KiB
Python
142 lines
5.1 KiB
Python
import hashlib
|
|
import os
|
|
|
|
from sqlalchemy import Boolean, Column, Date, DateTime, ForeignKey, Integer, String
|
|
from sqlalchemy.orm import declarative_base, relationship
|
|
|
|
Base = declarative_base()
|
|
|
|
|
|
# Async SQLAlchemy setup
|
|
def get_database_url(config=None):
|
|
db_url = None
|
|
if config and "database" in config and "url" in config["database"]:
|
|
db_url = config["database"]["url"]
|
|
if not db_url:
|
|
db_url = os.environ.get("DATABASE_URL")
|
|
if not db_url:
|
|
db_url = "sqlite+aiosqlite:///alpinebits.db"
|
|
return db_url
|
|
|
|
|
|
class Customer(Base):
|
|
__tablename__ = "customers"
|
|
id = Column(Integer, primary_key=True)
|
|
given_name = Column(String)
|
|
contact_id = Column(String, unique=True)
|
|
surname = Column(String)
|
|
name_prefix = Column(String)
|
|
email_address = Column(String)
|
|
phone = Column(String)
|
|
email_newsletter = Column(Boolean)
|
|
address_line = Column(String)
|
|
city_name = Column(String)
|
|
postal_code = Column(String)
|
|
country_code = Column(String)
|
|
gender = Column(String)
|
|
birth_date = Column(String)
|
|
language = Column(String)
|
|
address_catalog = Column(Boolean) # Added for XML
|
|
name_title = Column(String) # Added for XML
|
|
reservations = relationship("Reservation", back_populates="customer")
|
|
|
|
@staticmethod
|
|
def _normalize_and_hash(value):
|
|
"""Normalize and hash a value according to Meta Conversion API requirements."""
|
|
if not value:
|
|
return None
|
|
# Normalize: lowercase, strip whitespace
|
|
normalized = str(value).lower().strip()
|
|
# Remove spaces for phone numbers
|
|
is_phone = normalized.startswith("+") or normalized.replace(
|
|
"-", ""
|
|
).replace(" ", "").isdigit()
|
|
if is_phone:
|
|
chars_to_remove = [" ", "-", "(", ")"]
|
|
for char in chars_to_remove:
|
|
normalized = normalized.replace(char, "")
|
|
# SHA256 hash
|
|
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
|
|
|
|
def create_hashed_customer(self):
|
|
"""Create a HashedCustomer instance from this Customer."""
|
|
return HashedCustomer(
|
|
customer_id=self.id,
|
|
contact_id=self.contact_id,
|
|
hashed_email=self._normalize_and_hash(self.email_address),
|
|
hashed_phone=self._normalize_and_hash(self.phone),
|
|
hashed_given_name=self._normalize_and_hash(self.given_name),
|
|
hashed_surname=self._normalize_and_hash(self.surname),
|
|
hashed_city=self._normalize_and_hash(self.city_name),
|
|
hashed_postal_code=self._normalize_and_hash(self.postal_code),
|
|
hashed_country_code=self._normalize_and_hash(self.country_code),
|
|
hashed_gender=self._normalize_and_hash(self.gender),
|
|
hashed_birth_date=self._normalize_and_hash(self.birth_date),
|
|
)
|
|
|
|
|
|
class HashedCustomer(Base):
|
|
"""Hashed customer data for Meta Conversion API.
|
|
|
|
Stores SHA256 hashed versions of customer PII according to Meta's requirements.
|
|
This allows sending conversion events without exposing raw customer data.
|
|
"""
|
|
|
|
__tablename__ = "hashed_customers"
|
|
id = Column(Integer, primary_key=True)
|
|
customer_id = Column(
|
|
Integer, ForeignKey("customers.id"), unique=True, nullable=False
|
|
)
|
|
contact_id = Column(String, unique=True) # Keep unhashed for reference
|
|
hashed_email = Column(String(64)) # SHA256 produces 64 hex chars
|
|
hashed_phone = Column(String(64))
|
|
hashed_given_name = Column(String(64))
|
|
hashed_surname = Column(String(64))
|
|
hashed_city = Column(String(64))
|
|
hashed_postal_code = Column(String(64))
|
|
hashed_country_code = Column(String(64))
|
|
hashed_gender = Column(String(64))
|
|
hashed_birth_date = Column(String(64))
|
|
created_at = Column(DateTime)
|
|
|
|
customer = relationship("Customer", backref="hashed_version")
|
|
|
|
|
|
class Reservation(Base):
|
|
__tablename__ = "reservations"
|
|
id = Column(Integer, primary_key=True)
|
|
customer_id = Column(Integer, ForeignKey("customers.id"))
|
|
unique_id = Column(String, unique=True)
|
|
md5_unique_id = Column(String(32), unique=True) # max length 32 guaranteed
|
|
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)
|
|
created_at = Column(DateTime)
|
|
# Add all UTM fields and user comment for XML
|
|
utm_source = Column(String)
|
|
utm_medium = Column(String)
|
|
utm_campaign = Column(String)
|
|
utm_term = Column(String)
|
|
utm_content = Column(String)
|
|
user_comment = Column(String)
|
|
fbclid = Column(String)
|
|
gclid = Column(String)
|
|
# Add hotel_code and hotel_name for XML
|
|
hotel_code = Column(String)
|
|
hotel_name = Column(String)
|
|
customer = relationship("Customer", back_populates="reservations")
|
|
|
|
|
|
# Table for tracking acknowledged requests by client
|
|
class AckedRequest(Base):
|
|
__tablename__ = "acked_requests"
|
|
id = Column(Integer, primary_key=True)
|
|
client_id = Column(String, index=True)
|
|
unique_id = Column(
|
|
String, index=True
|
|
) # Should match Reservation.form_id or another unique field
|
|
timestamp = Column(DateTime)
|