Created a listener for wix-form to do push actions with but unsure how to best handle it
This commit is contained in:
@@ -14,6 +14,7 @@ dependencies = [
|
|||||||
"dotenv>=0.9.9",
|
"dotenv>=0.9.9",
|
||||||
"fastapi>=0.117.1",
|
"fastapi>=0.117.1",
|
||||||
"generateds>=2.44.3",
|
"generateds>=2.44.3",
|
||||||
|
"httpx>=0.28.1",
|
||||||
"lxml>=6.0.1",
|
"lxml>=6.0.1",
|
||||||
"pytest>=8.4.2",
|
"pytest>=8.4.2",
|
||||||
"redis>=6.4.0",
|
"redis>=6.4.0",
|
||||||
|
|||||||
@@ -614,18 +614,35 @@ class NotifReportReadAction(AlpineBitsAction):
|
|||||||
await dbsession.commit()
|
await dbsession.commit()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return AlpineBitsResponse(
|
return AlpineBitsResponse(
|
||||||
response_xml, HttpStatusCode.OK
|
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:
|
class AlpineBitsServer:
|
||||||
"""
|
"""
|
||||||
Asynchronous AlpineBits server for handling hotel data exchange requests.
|
Asynchronous AlpineBits server for handling hotel data exchange requests.
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ import xml.etree.ElementTree as ET
|
|||||||
from .alpinebits_server import AlpineBitsClientInfo, AlpineBitsServer, Version
|
from .alpinebits_server import AlpineBitsClientInfo, AlpineBitsServer, Version
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
||||||
|
from functools import partial
|
||||||
|
import httpx
|
||||||
|
|
||||||
from .db import (
|
from .db import (
|
||||||
Base,
|
Base,
|
||||||
@@ -52,9 +54,46 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
# HTTP Basic auth for AlpineBits
|
# HTTP Basic auth for AlpineBits
|
||||||
security_basic = HTTPBasic()
|
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
|
# 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
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
# Setup DB
|
# Setup DB
|
||||||
@@ -68,10 +107,19 @@ async def lifespan(app: FastAPI):
|
|||||||
DATABASE_URL = get_database_url(config)
|
DATABASE_URL = get_database_url(config)
|
||||||
engine = create_async_engine(DATABASE_URL, echo=True)
|
engine = create_async_engine(DATABASE_URL, echo=True)
|
||||||
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)
|
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)
|
||||||
|
|
||||||
app.state.engine = engine
|
app.state.engine = engine
|
||||||
app.state.async_sessionmaker = AsyncSessionLocal
|
app.state.async_sessionmaker = AsyncSessionLocal
|
||||||
app.state.config = config
|
app.state.config = config
|
||||||
app.state.alpine_bits_server = AlpineBitsServer(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
|
# Create tables
|
||||||
async with engine.begin() as conn:
|
async with engine.begin() as conn:
|
||||||
@@ -342,6 +390,12 @@ async def process_wix_form_submission(request: Request, data: Dict[str, Any], db
|
|||||||
await db.commit()
|
await db.commit()
|
||||||
await db.refresh(db_reservation)
|
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 {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"message": "Wix form data received successfully",
|
"message": "Wix form data received successfully",
|
||||||
|
|||||||
30
uv.lock
generated
30
uv.lock
generated
@@ -24,6 +24,7 @@ dependencies = [
|
|||||||
{ name = "dotenv" },
|
{ name = "dotenv" },
|
||||||
{ name = "fastapi" },
|
{ name = "fastapi" },
|
||||||
{ name = "generateds" },
|
{ name = "generateds" },
|
||||||
|
{ name = "httpx" },
|
||||||
{ name = "lxml" },
|
{ name = "lxml" },
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
{ name = "redis" },
|
{ name = "redis" },
|
||||||
@@ -43,6 +44,7 @@ requires-dist = [
|
|||||||
{ name = "dotenv", specifier = ">=0.9.9" },
|
{ name = "dotenv", specifier = ">=0.9.9" },
|
||||||
{ name = "fastapi", specifier = ">=0.117.1" },
|
{ name = "fastapi", specifier = ">=0.117.1" },
|
||||||
{ name = "generateds", specifier = ">=2.44.3" },
|
{ name = "generateds", specifier = ">=2.44.3" },
|
||||||
|
{ name = "httpx", specifier = ">=0.28.1" },
|
||||||
{ name = "lxml", specifier = ">=6.0.1" },
|
{ name = "lxml", specifier = ">=6.0.1" },
|
||||||
{ name = "pytest", specifier = ">=8.4.2" },
|
{ name = "pytest", specifier = ">=8.4.2" },
|
||||||
{ name = "redis", specifier = ">=6.4.0" },
|
{ 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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.10"
|
version = "3.10"
|
||||||
|
|||||||
Reference in New Issue
Block a user