Improve backend
This commit is contained in:
174
services/forecasting/app/api/webhooks.py
Normal file
174
services/forecasting/app/api/webhooks.py
Normal file
@@ -0,0 +1,174 @@
|
||||
# ================================================================
|
||||
# services/forecasting/app/api/webhooks.py
|
||||
# ================================================================
|
||||
"""
|
||||
Webhooks API - Receive events from other services
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status, Header
|
||||
from typing import Dict, Any, Optional
|
||||
from uuid import UUID
|
||||
from datetime import date
|
||||
import structlog
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from app.jobs.sales_data_listener import (
|
||||
handle_sales_import_completion,
|
||||
handle_pos_sync_completion
|
||||
)
|
||||
from shared.routing import RouteBuilder
|
||||
|
||||
route_builder = RouteBuilder('forecasting')
|
||||
router = APIRouter(tags=["webhooks"])
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
# ================================================================
|
||||
# Request Schemas
|
||||
# ================================================================
|
||||
|
||||
class SalesImportWebhook(BaseModel):
|
||||
"""Webhook payload for sales data import completion"""
|
||||
tenant_id: UUID = Field(..., description="Tenant ID")
|
||||
import_job_id: str = Field(..., description="Import job ID")
|
||||
start_date: date = Field(..., description="Start date of imported data")
|
||||
end_date: date = Field(..., description="End date of imported data")
|
||||
records_count: int = Field(..., ge=0, description="Number of records imported")
|
||||
import_source: str = Field(default="import", description="Source of import")
|
||||
|
||||
|
||||
class POSSyncWebhook(BaseModel):
|
||||
"""Webhook payload for POS sync completion"""
|
||||
tenant_id: UUID = Field(..., description="Tenant ID")
|
||||
sync_log_id: str = Field(..., description="POS sync log ID")
|
||||
sync_date: date = Field(..., description="Date of synced data")
|
||||
records_synced: int = Field(..., ge=0, description="Number of records synced")
|
||||
|
||||
|
||||
# ================================================================
|
||||
# Endpoints
|
||||
# ================================================================
|
||||
|
||||
@router.post(
|
||||
"/webhooks/sales-import-completed",
|
||||
status_code=status.HTTP_202_ACCEPTED
|
||||
)
|
||||
async def sales_import_completed_webhook(
|
||||
payload: SalesImportWebhook,
|
||||
x_webhook_signature: Optional[str] = Header(None, description="Webhook signature for verification")
|
||||
):
|
||||
"""
|
||||
Webhook endpoint for sales data import completion
|
||||
|
||||
Called by the sales service when a data import completes.
|
||||
Triggers validation backfill for the imported date range.
|
||||
|
||||
Note: In production, this should verify the webhook signature
|
||||
to ensure the request comes from a trusted source.
|
||||
"""
|
||||
try:
|
||||
logger.info(
|
||||
"Received sales import completion webhook",
|
||||
tenant_id=payload.tenant_id,
|
||||
import_job_id=payload.import_job_id,
|
||||
date_range=f"{payload.start_date} to {payload.end_date}"
|
||||
)
|
||||
|
||||
# In production, verify webhook signature here
|
||||
# if not verify_webhook_signature(x_webhook_signature, payload):
|
||||
# raise HTTPException(status_code=401, detail="Invalid webhook signature")
|
||||
|
||||
# Handle the import completion asynchronously
|
||||
result = await handle_sales_import_completion(
|
||||
tenant_id=payload.tenant_id,
|
||||
import_job_id=payload.import_job_id,
|
||||
start_date=payload.start_date,
|
||||
end_date=payload.end_date,
|
||||
records_count=payload.records_count,
|
||||
import_source=payload.import_source
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "accepted",
|
||||
"message": "Sales import completion event received and processing",
|
||||
"result": result
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Failed to process sales import webhook",
|
||||
payload=payload.dict(),
|
||||
error=str(e)
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to process webhook: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/webhooks/pos-sync-completed",
|
||||
status_code=status.HTTP_202_ACCEPTED
|
||||
)
|
||||
async def pos_sync_completed_webhook(
|
||||
payload: POSSyncWebhook,
|
||||
x_webhook_signature: Optional[str] = Header(None, description="Webhook signature for verification")
|
||||
):
|
||||
"""
|
||||
Webhook endpoint for POS sync completion
|
||||
|
||||
Called by the POS service when data synchronization completes.
|
||||
Triggers validation for the synced date.
|
||||
"""
|
||||
try:
|
||||
logger.info(
|
||||
"Received POS sync completion webhook",
|
||||
tenant_id=payload.tenant_id,
|
||||
sync_log_id=payload.sync_log_id,
|
||||
sync_date=payload.sync_date.isoformat()
|
||||
)
|
||||
|
||||
# In production, verify webhook signature here
|
||||
# if not verify_webhook_signature(x_webhook_signature, payload):
|
||||
# raise HTTPException(status_code=401, detail="Invalid webhook signature")
|
||||
|
||||
# Handle the sync completion
|
||||
result = await handle_pos_sync_completion(
|
||||
tenant_id=payload.tenant_id,
|
||||
sync_log_id=payload.sync_log_id,
|
||||
sync_date=payload.sync_date,
|
||||
records_synced=payload.records_synced
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "accepted",
|
||||
"message": "POS sync completion event received and processing",
|
||||
"result": result
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Failed to process POS sync webhook",
|
||||
payload=payload.dict(),
|
||||
error=str(e)
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to process webhook: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/webhooks/health",
|
||||
status_code=status.HTTP_200_OK
|
||||
)
|
||||
async def webhook_health_check():
|
||||
"""Health check endpoint for webhook receiver"""
|
||||
return {
|
||||
"status": "healthy",
|
||||
"service": "forecasting-webhooks",
|
||||
"endpoints": [
|
||||
"/webhooks/sales-import-completed",
|
||||
"/webhooks/pos-sync-completed"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user