From 36c32c44d81cd14d00d1979afb6a902d713192a1 Mon Sep 17 00:00:00 2001 From: Jonas Linter Date: Wed, 1 Oct 2025 16:32:15 +0200 Subject: [PATCH] Created a listener for wix-form to do push actions with but unsure how to best handle it --- pyproject.toml | 1 + src/alpine_bits_python/alpinebits_server.py | 33 ++++++++++--- src/alpine_bits_python/api.py | 54 +++++++++++++++++++++ uv.lock | 30 ++++++++++++ 4 files changed, 110 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 06a57dc..e628069 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ dependencies = [ "dotenv>=0.9.9", "fastapi>=0.117.1", "generateds>=2.44.3", + "httpx>=0.28.1", "lxml>=6.0.1", "pytest>=8.4.2", "redis>=6.4.0", diff --git a/src/alpine_bits_python/alpinebits_server.py b/src/alpine_bits_python/alpinebits_server.py index 10e7f3e..b8c2e09 100644 --- a/src/alpine_bits_python/alpinebits_server.py +++ b/src/alpine_bits_python/alpinebits_server.py @@ -613,18 +613,35 @@ class NotifReportReadAction(AlpineBitsAction): await dbsession.commit() - - - - - - - - return AlpineBitsResponse( response_xml, HttpStatusCode.OK ) + + +class PushAction(AlpineBitsAction): + """Creates the necessary xml for OTA_HotelResNotif:GuestRequests""" + + def __init__(self, config: Dict = {}): + self.name = AlpineBitsActionName.OTA_HOTEL_RES_NOTIF_GUEST_REQUESTS + self.version = [Version.V2024_10, Version.V2022_10] + self.config = config + + async def handle( + self, + action: str, + request_xml: str, + version: Version, + client_info: AlpineBitsClientInfo, + dbsession=None, + server_capabilities=None, + ) -> AlpineBitsResponse: + """Create push request XML.""" + + pass + + + class AlpineBitsServer: """ diff --git a/src/alpine_bits_python/api.py b/src/alpine_bits_python/api.py index 2490f73..16e7032 100644 --- a/src/alpine_bits_python/api.py +++ b/src/alpine_bits_python/api.py @@ -36,6 +36,8 @@ import xml.etree.ElementTree as ET from .alpinebits_server import AlpineBitsClientInfo, AlpineBitsServer, Version import urllib.parse from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker +from functools import partial +import httpx from .db import ( Base, @@ -52,9 +54,46 @@ _LOGGER = logging.getLogger(__name__) # HTTP Basic auth for AlpineBits security_basic = HTTPBasic() +from collections import defaultdict + +# --- Simple event dispatcher --- +class EventDispatcher: + def __init__(self): + self.listeners = defaultdict(list) + def register(self, event_name, func): + self.listeners[event_name].append(func) + async def dispatch(self, event_name, *args, **kwargs): + for func in self.listeners[event_name]: + await func(*args, **kwargs) + +event_dispatcher = EventDispatcher() + # Load config at startup +async def push_listener(customer, reservation, hotel, push): + + + server: AlpineBitsServer = app.state.alpine_bits_server + + hotel_id = hotel['hotel_id'] + + + + + + + + + + headers = {"Authorization": f"Bearer {push.get('token','')}"} if push.get('token') else {} + try: + async with httpx.AsyncClient() as client: + resp = await client.post(push["url"], json=payload, headers=headers, timeout=10) + _LOGGER.info(f"Push event fired to {push['url']} for hotel {hotel['hotel_id']}, status: {resp.status_code}") + except Exception as e: + _LOGGER.error(f"Push event failed for hotel {hotel['hotel_id']}: {e}") + @asynccontextmanager async def lifespan(app: FastAPI): # Setup DB @@ -68,10 +107,19 @@ async def lifespan(app: FastAPI): DATABASE_URL = get_database_url(config) engine = create_async_engine(DATABASE_URL, echo=True) AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False) + app.state.engine = engine app.state.async_sessionmaker = AsyncSessionLocal app.state.config = config app.state.alpine_bits_server = AlpineBitsServer(config) + app.state.event_dispatcher = event_dispatcher + + # Register push listeners for hotels with push_endpoint + for hotel in config.get("alpine_bits_auth", []): + push = hotel.get("push_endpoint") + if push: + + event_dispatcher.register("form_processed", partial(push_listener, hotel=hotel, push=push)) # Create tables async with engine.begin() as conn: @@ -341,6 +389,12 @@ async def process_wix_form_submission(request: Request, data: Dict[str, Any], db db.add(db_reservation) await db.commit() await db.refresh(db_reservation) + + + # Fire event for listeners (push, etc.) + dispatcher = getattr(request.app.state, "event_dispatcher", None) + if dispatcher: + await dispatcher.dispatch("form_processed", db_customer, db_reservation) return { "status": "success", diff --git a/uv.lock b/uv.lock index b4adb07..f867c49 100644 --- a/uv.lock +++ b/uv.lock @@ -24,6 +24,7 @@ dependencies = [ { name = "dotenv" }, { name = "fastapi" }, { name = "generateds" }, + { name = "httpx" }, { name = "lxml" }, { name = "pytest" }, { name = "redis" }, @@ -43,6 +44,7 @@ requires-dist = [ { name = "dotenv", specifier = ">=0.9.9" }, { name = "fastapi", specifier = ">=0.117.1" }, { name = "generateds", specifier = ">=2.44.3" }, + { name = "httpx", specifier = ">=0.28.1" }, { name = "lxml", specifier = ">=6.0.1" }, { name = "pytest", specifier = ">=8.4.2" }, { name = "redis", specifier = ">=6.4.0" }, @@ -286,6 +288,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + [[package]] name = "idna" version = "3.10"