Linting and formatting
This commit is contained in:
@@ -1,62 +1,57 @@
|
||||
from fastapi import (
|
||||
FastAPI,
|
||||
HTTPException,
|
||||
BackgroundTasks,
|
||||
Request,
|
||||
Depends,
|
||||
APIRouter,
|
||||
Form,
|
||||
File,
|
||||
UploadFile,
|
||||
)
|
||||
from fastapi.concurrency import asynccontextmanager
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.security import HTTPBearer, HTTPBasicCredentials, HTTPBasic
|
||||
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 (
|
||||
generate_unique_id,
|
||||
validate_api_key,
|
||||
validate_wix_signature,
|
||||
generate_api_key,
|
||||
)
|
||||
from .rate_limit import (
|
||||
limiter,
|
||||
webhook_limiter,
|
||||
custom_rate_limit_handler,
|
||||
DEFAULT_RATE_LIMIT,
|
||||
WEBHOOK_RATE_LIMIT,
|
||||
BURST_RATE_LIMIT,
|
||||
)
|
||||
from slowapi.errors import RateLimitExceeded
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Optional, List
|
||||
import json
|
||||
import os
|
||||
import asyncio
|
||||
import gzip
|
||||
import xml.etree.ElementTree as ET
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import urllib.parse
|
||||
from datetime import UTC, date, datetime
|
||||
from functools import partial
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from fastapi import (
|
||||
APIRouter,
|
||||
Depends,
|
||||
FastAPI,
|
||||
HTTPException,
|
||||
Request,
|
||||
)
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import HTMLResponse, Response
|
||||
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||
from slowapi.errors import RateLimitExceeded
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||
|
||||
from .alpinebits_server import (
|
||||
AlpineBitsActionName,
|
||||
AlpineBitsClientInfo,
|
||||
AlpineBitsServer,
|
||||
Version,
|
||||
AlpineBitsActionName,
|
||||
)
|
||||
import urllib.parse
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
||||
from functools import partial
|
||||
import httpx
|
||||
|
||||
from .auth import (
|
||||
generate_api_key,
|
||||
generate_unique_id,
|
||||
validate_api_key,
|
||||
)
|
||||
from .config_loader import load_config
|
||||
from .db import (
|
||||
Base,
|
||||
Customer as DBCustomer,
|
||||
Reservation as DBReservation,
|
||||
get_database_url,
|
||||
)
|
||||
|
||||
from .db import (
|
||||
Customer as DBCustomer,
|
||||
)
|
||||
from .db import (
|
||||
Reservation as DBReservation,
|
||||
)
|
||||
from .rate_limit import (
|
||||
BURST_RATE_LIMIT,
|
||||
DEFAULT_RATE_LIMIT,
|
||||
WEBHOOK_RATE_LIMIT,
|
||||
custom_rate_limit_handler,
|
||||
limiter,
|
||||
webhook_limiter,
|
||||
)
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
@@ -98,8 +93,7 @@ event_dispatcher = EventDispatcher()
|
||||
|
||||
|
||||
async def push_listener(customer: DBCustomer, reservation: DBReservation, hotel):
|
||||
"""
|
||||
Push listener that sends reservation data to hotel's push endpoint.
|
||||
"""Push listener that sends reservation data to hotel's push endpoint.
|
||||
Only called for reservations that match this hotel's hotel_id.
|
||||
"""
|
||||
push_endpoint = hotel.get("push_endpoint")
|
||||
@@ -186,7 +180,7 @@ async def lifespan(app: FastAPI):
|
||||
try:
|
||||
config = load_config()
|
||||
except Exception as e:
|
||||
_LOGGER.error(f"Failed to load config: {str(e)}")
|
||||
_LOGGER.error(f"Failed to load config: {e!s}")
|
||||
config = {}
|
||||
|
||||
DATABASE_URL = get_database_url(config)
|
||||
@@ -263,9 +257,8 @@ app.add_middleware(
|
||||
)
|
||||
|
||||
|
||||
async def process_form_submission(submission_data: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Background task to process the form submission.
|
||||
async def process_form_submission(submission_data: dict[str, Any]) -> None:
|
||||
"""Background task to process the form submission.
|
||||
Add your business logic here.
|
||||
"""
|
||||
try:
|
||||
@@ -297,7 +290,7 @@ async def process_form_submission(submission_data: Dict[str, Any]) -> None:
|
||||
# - Process the data further
|
||||
|
||||
except Exception as e:
|
||||
_LOGGER.error(f"Error processing form submission: {str(e)}")
|
||||
_LOGGER.error(f"Error processing form submission: {e!s}")
|
||||
|
||||
|
||||
@api_router.get("/")
|
||||
@@ -332,9 +325,8 @@ async def health_check(request: Request):
|
||||
|
||||
|
||||
# Extracted business logic for handling Wix form submissions
|
||||
async def process_wix_form_submission(request: Request, data: Dict[str, Any], db):
|
||||
"""
|
||||
Shared business logic for handling Wix form submissions (test and production).
|
||||
async def process_wix_form_submission(request: Request, data: dict[str, Any], db):
|
||||
"""Shared business logic for handling Wix form submissions (test and production).
|
||||
"""
|
||||
timestamp = datetime.now().isoformat()
|
||||
|
||||
@@ -485,7 +477,7 @@ async def process_wix_form_submission(request: Request, data: Dict[str, Any], db
|
||||
num_children=num_children,
|
||||
children_ages=",".join(str(a) for a in children_ages),
|
||||
offer=offer,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
created_at=datetime.now(UTC),
|
||||
utm_source=data.get("field:utm_source"),
|
||||
utm_medium=data.get("field:utm_medium"),
|
||||
utm_campaign=data.get("field:utm_campaign"),
|
||||
@@ -532,38 +524,36 @@ async def process_wix_form_submission(request: Request, data: Dict[str, Any], db
|
||||
@api_router.post("/webhook/wix-form")
|
||||
@webhook_limiter.limit(WEBHOOK_RATE_LIMIT)
|
||||
async def handle_wix_form(
|
||||
request: Request, data: Dict[str, Any], db_session=Depends(get_async_session)
|
||||
request: Request, data: dict[str, Any], db_session=Depends(get_async_session)
|
||||
):
|
||||
"""
|
||||
Unified endpoint to handle Wix form submissions (test and production).
|
||||
"""Unified endpoint to handle Wix form submissions (test and production).
|
||||
No authentication required for this endpoint.
|
||||
"""
|
||||
try:
|
||||
return await process_wix_form_submission(request, data, db_session)
|
||||
except Exception as e:
|
||||
_LOGGER.error(f"Error in handle_wix_form: {str(e)}")
|
||||
_LOGGER.error(f"Error in handle_wix_form: {e!s}")
|
||||
# log stacktrace
|
||||
import traceback
|
||||
|
||||
traceback_str = traceback.format_exc()
|
||||
_LOGGER.error(f"Stack trace for handle_wix_form: {traceback_str}")
|
||||
raise HTTPException(status_code=500, detail=f"Error processing Wix form data")
|
||||
raise HTTPException(status_code=500, detail="Error processing Wix form data")
|
||||
|
||||
|
||||
@api_router.post("/webhook/wix-form/test")
|
||||
@limiter.limit(DEFAULT_RATE_LIMIT)
|
||||
async def handle_wix_form_test(
|
||||
request: Request, data: Dict[str, Any], db_session=Depends(get_async_session)
|
||||
request: Request, data: dict[str, Any], db_session=Depends(get_async_session)
|
||||
):
|
||||
"""
|
||||
Test endpoint to verify the API is working with raw JSON data.
|
||||
"""Test endpoint to verify the API is working with raw JSON data.
|
||||
No authentication required for testing purposes.
|
||||
"""
|
||||
try:
|
||||
return await process_wix_form_submission(request, data, db_session)
|
||||
except Exception as e:
|
||||
_LOGGER.error(f"Error in handle_wix_form_test: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Error processing test data")
|
||||
_LOGGER.error(f"Error in handle_wix_form_test: {e!s}")
|
||||
raise HTTPException(status_code=500, detail="Error processing test data")
|
||||
|
||||
|
||||
@api_router.post("/admin/generate-api-key")
|
||||
@@ -571,8 +561,7 @@ async def handle_wix_form_test(
|
||||
async def generate_new_api_key(
|
||||
request: Request, admin_key: str = Depends(validate_api_key)
|
||||
):
|
||||
"""
|
||||
Admin endpoint to generate new API keys.
|
||||
"""Admin endpoint to generate new API keys.
|
||||
Requires admin API key and is heavily rate limited.
|
||||
"""
|
||||
if admin_key != "admin-key":
|
||||
@@ -593,8 +582,7 @@ async def generate_new_api_key(
|
||||
async def validate_basic_auth(
|
||||
credentials: HTTPBasicCredentials = Depends(security_basic),
|
||||
) -> str:
|
||||
"""
|
||||
Validate basic authentication for AlpineBits protocol.
|
||||
"""Validate basic authentication for AlpineBits protocol.
|
||||
Returns username if valid, raises HTTPException if not.
|
||||
"""
|
||||
# Accept any username/password pair present in config['alpine_bits_auth']
|
||||
@@ -626,9 +614,8 @@ async def validate_basic_auth(
|
||||
return credentials.username, credentials.password
|
||||
|
||||
|
||||
def parse_multipart_data(content_type: str, body: bytes) -> Dict[str, Any]:
|
||||
"""
|
||||
Parse multipart/form-data from raw request body.
|
||||
def parse_multipart_data(content_type: str, body: bytes) -> dict[str, Any]:
|
||||
"""Parse multipart/form-data from raw request body.
|
||||
This is a simplified parser for the AlpineBits use case.
|
||||
"""
|
||||
if "multipart/form-data" not in content_type:
|
||||
@@ -692,8 +679,7 @@ async def alpinebits_server_handshake(
|
||||
credentials_tupel: tuple = Depends(validate_basic_auth),
|
||||
dbsession=Depends(get_async_session),
|
||||
):
|
||||
"""
|
||||
AlpineBits server endpoint implementing the handshake protocol.
|
||||
"""AlpineBits server endpoint implementing the handshake protocol.
|
||||
|
||||
This endpoint handles:
|
||||
- Protocol version negotiation via X-AlpineBits-ClientProtocolVersion header
|
||||
@@ -747,10 +733,10 @@ async def alpinebits_server_handshake(
|
||||
try:
|
||||
body = gzip.decompress(body)
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"ERROR: Failed to decompress gzip content",
|
||||
detail="ERROR: Failed to decompress gzip content",
|
||||
)
|
||||
|
||||
# Check content type (after decompression)
|
||||
@@ -767,10 +753,10 @@ async def alpinebits_server_handshake(
|
||||
if "multipart/form-data" in content_type:
|
||||
try:
|
||||
form_data = parse_multipart_data(content_type, body)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"ERROR: Failed to parse multipart/form-data",
|
||||
detail="ERROR: Failed to parse multipart/form-data",
|
||||
)
|
||||
elif "application/x-www-form-urlencoded" in content_type:
|
||||
# Parse as urlencoded
|
||||
@@ -829,15 +815,14 @@ async def alpinebits_server_handshake(
|
||||
# Re-raise HTTP exceptions (auth errors, etc.)
|
||||
raise
|
||||
except Exception as e:
|
||||
_LOGGER.error(f"Error in AlpineBits handshake: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Internal server error")
|
||||
_LOGGER.error(f"Error in AlpineBits handshake: {e!s}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
|
||||
@api_router.get("/admin/stats")
|
||||
@limiter.limit("10/minute")
|
||||
async def get_api_stats(request: Request, admin_key: str = Depends(validate_api_key)):
|
||||
"""
|
||||
Admin endpoint to get API usage statistics.
|
||||
"""Admin endpoint to get API usage statistics.
|
||||
Requires admin API key.
|
||||
"""
|
||||
if admin_key != "admin-key":
|
||||
@@ -862,8 +847,7 @@ app.include_router(api_router)
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def landing_page():
|
||||
"""
|
||||
Serve the under construction landing page at the root route
|
||||
"""Serve the under construction landing page at the root route
|
||||
"""
|
||||
try:
|
||||
# Get the path to the HTML file
|
||||
@@ -871,7 +855,7 @@ async def landing_page():
|
||||
|
||||
html_path = os.path.join(os.path.dirname(__file__), "templates", "index.html")
|
||||
|
||||
with open(html_path, "r", encoding="utf-8") as f:
|
||||
with open(html_path, encoding="utf-8") as f:
|
||||
html_content = f.read()
|
||||
|
||||
return HTMLResponse(content=html_content, status_code=200)
|
||||
|
||||
Reference in New Issue
Block a user