# AlpineBits Python Server - AI Agent Instructions ## Project Overview This is an **AlpineBits 2024-10 server** that bridges booking requests from Wix landing pages to hotel partners. It's a dual-purpose system: 1. **FastAPI webhook receiver** - accepts booking forms from wix.com landing pages via `/api/webhook/wix-form` 2. **AlpineBits OTA server** - exposes hotel reservation data at `/api/alpinebits/server-2024-10` using OpenTravel Alliance XML protocol Data flows: Wix form → Database → AlpineBits XML → Hotel systems (pull or push) ## Architecture Patterns ### XML Generation with xsdata - **Never manually construct XML strings**. Use xsdata-generated Pydantic dataclasses from `src/alpine_bits_python/generated/alpinebits.py` - Parse XML: `XmlParser().from_string(xml_string, OtaPingRq)` - Serialize XML: `XmlSerializer(config=SerializerConfig(...)).render(ota_object)` - Factory pattern: Use classes in `alpine_bits_helpers.py` (e.g., `CustomerFactory`, `GuestCountsFactory`) to build complex OTA objects from DB models - Example: `create_res_retrieve_response()` builds OTA_ResRetrieveRS from `(Reservation, Customer)` tuples - **Regenerating XML classes**: Run `xsdata` on `AlpineBits-HotelData-2024-10/files/schema-xsd/alpinebits.xsd` to regenerate `generated/alpinebits.py` (only if XSD spec changes) ### Configuration System - Config loaded from YAML with secret injection via `!secret` tags (see `config_loader.py`) - Default config location: `config/config.yaml` + `config/secrets.yaml` - Override via `ALPINEBITS_CONFIG_DIR` environment variable - Multi-hotel support: Each hotel in `alpine_bits_auth` array gets own credentials and optional `push_endpoint` ### Database Layer - **Async-only SQLAlchemy** with `AsyncSession` (see `db.py`) - Three core tables: `Customer`, `Reservation`, `AckedRequest` (tracks which clients acknowledged which reservations) - DB URL configurable: SQLite for dev (`sqlite+aiosqlite:///alpinebits.db`), PostgreSQL for prod - Database auto-created on startup in `api.py:create_app()` ### Event-Driven Push System - `EventDispatcher` in `api.py` enables hotel-specific listeners: `event_dispatcher.register_hotel_listener("reservation:created", hotel_code, push_listener)` - Push listener sends OTA_HotelResNotif XML to hotel's configured `push_endpoint.url` with Bearer token auth - Push requests logged to `logs/push_requests/` with timestamp and unique ID - **Note**: Push endpoint support is currently dormant - configured but not actively used by partners ### AlpineBits Action Pattern - Each OTA action is a class inheriting `AlpineBitsActionHandler` (see `alpinebits_server.py`) - Actions: `PingAction`, `ReadAction`, `NotifReportAction`, `PushAction` - Request flow: Parse XML → Call `handle()` → Return `AlpineBitsActionResult` with XML response + HTTP status - `AlpineBitsActionName` enum maps capability names to request names (e.g., `OTA_READ` → `"OTA_Read:GuestRequests"`) - Server supports multiple AlpineBits versions (2024-10, 2022-10) when actions are identical across versions ### Acknowledgment System - `AckedRequest` table tracks which clients acknowledged which reservations via `OTA_NotifReport:GuestRequests` - Read requests filter out acknowledged reservations for clients with `client_id` - Prevents duplicate reservation sends: once acknowledged, data won't appear in subsequent reads for that client ## Critical Workflows ### Running Locally ```bash uv sync # Install dependencies (uses uv, not pip!) uv run python -m alpine_bits_python.run_api # Start server on port 8080, clears DB on startup ``` ### Testing ```bash uv run pytest # Run all tests uv run pytest tests/test_alpine_bits_server_read.py # Specific test file ``` - Tests use in-memory SQLite via `test_db_engine` fixture (see `tests/test_alpine_bits_server_read.py`) - Test data fixtures in `tests/test_data/` directory ### Building for Deployment ```bash uv sync docker build . -t gitea.linter-home.com/jonas/asa_api:master ``` - Multi-stage Dockerfile: builder stage installs deps with uv, production stage copies `.venv` - Runs as non-root user (UID 1000) for security - Requires `ALPINEBITS_CONFIG_DIR=/config` volume mount for config files - **Deployment**: Docker build pipeline exists and works; can also build manually on target system ## Project-Specific Conventions ### Naming Patterns - OTA message types use full AlpineBits names: `OtaReadRq`, `OtaResRetrieveRs`, `OtaHotelResNotifRq` - Factory classes suffix with `Factory`: `CustomerFactory`, `HotelReservationIdFactory` - DB models in `db.py`, validation schemas in `schemas.py`, OTA helpers in `alpine_bits_helpers.py` ### Data Validation Flow 1. **API Layer** → Pydantic schemas (`schemas.py`) validate incoming data 2. **DB Layer** → SQLAlchemy models (`db.py`) persist validated data 3. **XML Layer** → xsdata classes (`generated/alpinebits.py`) + factories (`alpine_bits_helpers.py`) generate OTA XML This separation prevents mixing concerns (validation ≠ persistence ≠ XML generation). ### Unique ID Generation - Reservation IDs: 35-char max, format `{hotel_code}_{uuid4}_{timestamp}` - Generated via `generate_unique_id()` in `auth.py` ### Rate Limiting - Uses `slowapi` with Redis backend - Three tiers: `DEFAULT_RATE_LIMIT` (100/hour), `WEBHOOK_RATE_LIMIT` (300/hour), `BURST_RATE_LIMIT` (10/minute) - Applied via decorators: `@limiter.limit(DEFAULT_RATE_LIMIT)` ## Common Pitfalls 1. **Don't use synchronous SQLAlchemy calls** - Always `await session.execute()`, never `session.query()` 2. **Don't hardcode XML namespaces** - Let xsdata handle them via generated classes 3. **Don't skip config validation** - Voluptuous schemas in `config_loader.py` catch config errors early 4. **Auth is per-hotel** - HTTP Basic Auth credentials from `alpine_bits_auth` config array 5. **AlpineBits version matters** - Server implements 2024-10 spec (see `AlpineBits-HotelData-2024-10/` directory) ## Key Files Reference - `api.py` - FastAPI app, all endpoints, event dispatcher - `alpinebits_server.py` - AlpineBits action handlers (Ping, Read, NotifReport) - `alpine_bits_helpers.py` - Factory classes for building OTA XML from DB models - `config_loader.py` - YAML config loading with secret injection - `db.py` - SQLAlchemy async models (Customer, Reservation, AckedRequest) - `schemas.py` - Pydantic validation schemas - `generated/alpinebits.py` - xsdata-generated OTA XML classes (DO NOT EDIT - regenerate from XSD) ## Testing Strategy - Fixtures create isolated in-memory databases per test - Use `test_config()` fixture for test configuration - XML serialization/parsing tested via xsdata round-trips - Push endpoint mocking via httpx in tests