Created endpoint for export
This commit is contained in:
@@ -7,6 +7,7 @@ import urllib.parse
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import UTC, date, datetime
|
from datetime import UTC, date, datetime
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
@@ -27,10 +28,9 @@ from .alpinebits_server import (
|
|||||||
)
|
)
|
||||||
from .auth import generate_api_key, generate_unique_id, validate_api_key
|
from .auth import generate_api_key, generate_unique_id, validate_api_key
|
||||||
from .config_loader import load_config
|
from .config_loader import load_config
|
||||||
from .db import Base
|
from .db import Base, get_database_url
|
||||||
from .db import Customer as DBCustomer
|
from .db import Customer as DBCustomer
|
||||||
from .db import Reservation as DBReservation
|
from .db import Reservation as DBReservation
|
||||||
from .db import get_database_url
|
|
||||||
from .rate_limit import (
|
from .rate_limit import (
|
||||||
BURST_RATE_LIMIT,
|
BURST_RATE_LIMIT,
|
||||||
DEFAULT_RATE_LIMIT,
|
DEFAULT_RATE_LIMIT,
|
||||||
@@ -476,65 +476,6 @@ 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)
|
|
||||||
):
|
|
||||||
"""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: {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="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)
|
|
||||||
):
|
|
||||||
"""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: {e!s}")
|
|
||||||
raise HTTPException(status_code=500, detail="Error processing test data")
|
|
||||||
|
|
||||||
|
|
||||||
# UNUSED
|
|
||||||
@api_router.post("/admin/generate-api-key")
|
|
||||||
@limiter.limit("5/hour") # Very restrictive for admin operations
|
|
||||||
async def generate_new_api_key(
|
|
||||||
request: Request, admin_key: str = Depends(validate_api_key)
|
|
||||||
):
|
|
||||||
"""Admin endpoint to generate new API keys.
|
|
||||||
Requires admin API key and is heavily rate limited.
|
|
||||||
"""
|
|
||||||
if admin_key != "admin-key":
|
|
||||||
raise HTTPException(status_code=403, detail="Admin access required")
|
|
||||||
|
|
||||||
new_key = generate_api_key()
|
|
||||||
_LOGGER.info(f"Generated new API key (requested by: {admin_key})")
|
|
||||||
|
|
||||||
return {
|
|
||||||
"status": "success",
|
|
||||||
"message": "New API key generated",
|
|
||||||
"api_key": new_key,
|
|
||||||
"timestamp": datetime.now().isoformat(),
|
|
||||||
"note": "Store this key securely - it won't be shown again",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def validate_basic_auth(
|
async def validate_basic_auth(
|
||||||
credentials: HTTPBasicCredentials = Depends(security_basic),
|
credentials: HTTPBasicCredentials = Depends(security_basic),
|
||||||
) -> str:
|
) -> str:
|
||||||
@@ -572,6 +513,128 @@ async def validate_basic_auth(
|
|||||||
return credentials.username, credentials.password
|
return credentials.username, credentials.password
|
||||||
|
|
||||||
|
|
||||||
|
@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)
|
||||||
|
):
|
||||||
|
"""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: {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="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)
|
||||||
|
):
|
||||||
|
"""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: {e!s}")
|
||||||
|
raise HTTPException(status_code=500, detail="Error processing test data")
|
||||||
|
|
||||||
|
|
||||||
|
@api_router.post("/hoteldata/conversions_import")
|
||||||
|
@limiter.limit(DEFAULT_RATE_LIMIT)
|
||||||
|
async def handle_xml_upload(
|
||||||
|
request: Request, credentials_tupel: tuple = Depends(validate_basic_auth)
|
||||||
|
):
|
||||||
|
"""Endpoint for receiving XML files for conversion processing.
|
||||||
|
Requires basic authentication and saves XML files to log directory.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get the raw body content
|
||||||
|
body = await request.body()
|
||||||
|
|
||||||
|
if not body:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400, detail="ERROR: No XML content provided"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Try to decode as UTF-8
|
||||||
|
try:
|
||||||
|
xml_content = body.decode("utf-8")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
# If UTF-8 fails, try with latin-1 as fallback
|
||||||
|
xml_content = body.decode("latin-1")
|
||||||
|
|
||||||
|
# Basic validation that it's XML-like
|
||||||
|
if not xml_content.strip().startswith("<"):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400, detail="ERROR: Content does not appear to be XML"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create logs directory for XML conversions
|
||||||
|
logs_dir = Path("logs/conversions_import")
|
||||||
|
if not logs_dir.exists():
|
||||||
|
logs_dir.mkdir(parents=True, mode=0o755, exist_ok=True)
|
||||||
|
_LOGGER.info("Created directory: %s", logs_dir)
|
||||||
|
|
||||||
|
# Generate filename with timestamp and authenticated user
|
||||||
|
username, _ = credentials_tupel
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
log_filename = logs_dir / f"xml_import_{username}_{timestamp}.xml"
|
||||||
|
|
||||||
|
# Save XML content to file
|
||||||
|
log_filename.write_text(xml_content, encoding="utf-8")
|
||||||
|
|
||||||
|
_LOGGER.info("XML file saved to %s by user %s", log_filename, username)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": "XML file received and saved",
|
||||||
|
"filename": log_filename.name,
|
||||||
|
"size_bytes": len(body),
|
||||||
|
"authenticated_user": username,
|
||||||
|
}
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
_LOGGER.exception("Error in handle_xml_upload")
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500, detail="Error processing XML upload"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
|
||||||
|
# UNUSED
|
||||||
|
@api_router.post("/admin/generate-api-key")
|
||||||
|
@limiter.limit("5/hour") # Very restrictive for admin operations
|
||||||
|
async def generate_new_api_key(
|
||||||
|
request: Request, admin_key: str = Depends(validate_api_key)
|
||||||
|
):
|
||||||
|
"""Admin endpoint to generate new API keys.
|
||||||
|
Requires admin API key and is heavily rate limited.
|
||||||
|
"""
|
||||||
|
if admin_key != "admin-key":
|
||||||
|
raise HTTPException(status_code=403, detail="Admin access required")
|
||||||
|
|
||||||
|
new_key = generate_api_key()
|
||||||
|
_LOGGER.info(f"Generated new API key (requested by: {admin_key})")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": "New API key generated",
|
||||||
|
"api_key": new_key,
|
||||||
|
"timestamp": datetime.now().isoformat(),
|
||||||
|
"note": "Store this key securely - it won't be shown again",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# TODO Bit sketchy. May need requests-toolkit in the future
|
# TODO Bit sketchy. May need requests-toolkit in the future
|
||||||
def parse_multipart_data(content_type: str, body: bytes) -> dict[str, Any]:
|
def parse_multipart_data(content_type: str, body: bytes) -> dict[str, Any]:
|
||||||
"""Parse multipart/form-data from raw request body.
|
"""Parse multipart/form-data from raw request body.
|
||||||
|
|||||||
Reference in New Issue
Block a user