feat: Add hotel and webhook endpoint management

- Introduced Hotel and WebhookEndpoint models to manage hotel configurations and webhook settings.
- Implemented sync_config_to_database function to synchronize hotel data from configuration to the database.
- Added HotelService for accessing hotel configurations and managing customer data.
- Created WebhookProcessor interface and specific processors for handling different webhook types (Wix form and generic).
- Enhanced webhook processing logic to handle incoming requests and create/update reservations and customers.
- Added logging for better traceability of operations related to hotels and webhooks.
This commit is contained in:
Jonas Linter
2025-11-25 12:05:48 +01:00
parent da85098d8d
commit 8d144a761c
8 changed files with 1706 additions and 262 deletions

View File

@@ -0,0 +1,119 @@
"""add_hotels_and_webhook_tables
Revision ID: e7ee03d8f430
Revises: a1b2c3d4e5f6
Create Date: 2025-11-25 11:55:18.872715
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'e7ee03d8f430'
down_revision: Union[str, Sequence[str], None] = 'a1b2c3d4e5f6'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# Create hotels table
op.create_table(
'hotels',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('hotel_id', sa.String(length=50), nullable=False),
sa.Column('hotel_name', sa.String(length=200), nullable=False),
sa.Column('username', sa.String(length=100), nullable=False),
sa.Column('password_hash', sa.String(length=200), nullable=False),
sa.Column('meta_account_id', sa.String(length=50), nullable=True),
sa.Column('google_account_id', sa.String(length=50), nullable=True),
sa.Column('push_endpoint_url', sa.String(length=500), nullable=True),
sa.Column('push_endpoint_token', sa.String(length=200), nullable=True),
sa.Column('push_endpoint_username', sa.String(length=100), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False, default=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_hotels_hotel_id'), 'hotels', ['hotel_id'], unique=True)
op.create_index(op.f('ix_hotels_username'), 'hotels', ['username'], unique=True)
op.create_index(op.f('ix_hotels_is_active'), 'hotels', ['is_active'], unique=False)
# Create webhook_endpoints table
op.create_table(
'webhook_endpoints',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('hotel_id', sa.String(length=50), nullable=False),
sa.Column('webhook_secret', sa.String(length=64), nullable=False),
sa.Column('webhook_type', sa.String(length=50), nullable=False),
sa.Column('description', sa.String(length=200), nullable=True),
sa.Column('is_enabled', sa.Boolean(), nullable=False, default=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['hotel_id'], ['hotels.hotel_id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_webhook_endpoints_hotel_id'), 'webhook_endpoints', ['hotel_id'], unique=False)
op.create_index(op.f('ix_webhook_endpoints_webhook_secret'), 'webhook_endpoints', ['webhook_secret'], unique=True)
op.create_index('idx_webhook_endpoint_hotel_type', 'webhook_endpoints', ['hotel_id', 'webhook_type'], unique=False)
# Create webhook_requests table
op.create_table(
'webhook_requests',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('payload_hash', sa.String(length=64), nullable=False),
sa.Column('webhook_endpoint_id', sa.Integer(), nullable=True),
sa.Column('hotel_id', sa.String(length=50), nullable=True),
sa.Column('status', sa.String(length=20), nullable=False, default='pending'),
sa.Column('processing_started_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('processing_completed_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('retry_count', sa.Integer(), nullable=True, default=0),
sa.Column('last_error', sa.String(length=2000), nullable=True),
sa.Column('payload_json', sa.JSON(), nullable=True),
sa.Column('payload_size_bytes', sa.Integer(), nullable=True),
sa.Column('purged_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('source_ip', sa.String(length=45), nullable=True),
sa.Column('user_agent', sa.String(length=500), nullable=True),
sa.Column('created_customer_id', sa.Integer(), nullable=True),
sa.Column('created_reservation_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['webhook_endpoint_id'], ['webhook_endpoints.id'], ),
sa.ForeignKeyConstraint(['hotel_id'], ['hotels.hotel_id'], ),
sa.ForeignKeyConstraint(['created_customer_id'], ['customers.id'], ),
sa.ForeignKeyConstraint(['created_reservation_id'], ['reservations.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_webhook_requests_payload_hash'), 'webhook_requests', ['payload_hash'], unique=True)
op.create_index(op.f('ix_webhook_requests_webhook_endpoint_id'), 'webhook_requests', ['webhook_endpoint_id'], unique=False)
op.create_index(op.f('ix_webhook_requests_hotel_id'), 'webhook_requests', ['hotel_id'], unique=False)
op.create_index(op.f('ix_webhook_requests_status'), 'webhook_requests', ['status'], unique=False)
op.create_index(op.f('ix_webhook_requests_created_at'), 'webhook_requests', ['created_at'], unique=False)
op.create_index('idx_webhook_status_created', 'webhook_requests', ['status', 'created_at'], unique=False)
op.create_index('idx_webhook_hotel_created', 'webhook_requests', ['hotel_id', 'created_at'], unique=False)
op.create_index('idx_webhook_purge_candidate', 'webhook_requests', ['status', 'purged_at', 'created_at'], unique=False)
def downgrade() -> None:
"""Downgrade schema."""
# Drop tables in reverse order (respecting foreign key constraints)
op.drop_index('idx_webhook_purge_candidate', table_name='webhook_requests')
op.drop_index('idx_webhook_hotel_created', table_name='webhook_requests')
op.drop_index('idx_webhook_status_created', table_name='webhook_requests')
op.drop_index(op.f('ix_webhook_requests_created_at'), table_name='webhook_requests')
op.drop_index(op.f('ix_webhook_requests_status'), table_name='webhook_requests')
op.drop_index(op.f('ix_webhook_requests_hotel_id'), table_name='webhook_requests')
op.drop_index(op.f('ix_webhook_requests_webhook_endpoint_id'), table_name='webhook_requests')
op.drop_index(op.f('ix_webhook_requests_payload_hash'), table_name='webhook_requests')
op.drop_table('webhook_requests')
op.drop_index('idx_webhook_endpoint_hotel_type', table_name='webhook_endpoints')
op.drop_index(op.f('ix_webhook_endpoints_webhook_secret'), table_name='webhook_endpoints')
op.drop_index(op.f('ix_webhook_endpoints_hotel_id'), table_name='webhook_endpoints')
op.drop_table('webhook_endpoints')
op.drop_index(op.f('ix_hotels_is_active'), table_name='hotels')
op.drop_index(op.f('ix_hotels_username'), table_name='hotels')
op.drop_index(op.f('ix_hotels_hotel_id'), table_name='hotels')
op.drop_table('hotels')