This commit is contained in:
Iliyan Angelov
2025-12-01 01:08:39 +02:00
parent 0fa2adeb19
commit 1a103a769f
234 changed files with 5513 additions and 283 deletions

View File

@@ -0,0 +1,4 @@
"""
Integration routes for webhooks and API keys.
"""

View File

@@ -0,0 +1,165 @@
"""
API key management routes.
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List, Optional
from datetime import datetime
from ...shared.config.database import get_db
from ...shared.config.logging_config import get_logger
from ...security.middleware.auth import get_current_user, authorize_roles
from ...auth.models.user import User
from ..services.api_key_service import api_key_service
from ..models.api_key import APIKey
from ...shared.utils.response_helpers import success_response
from pydantic import BaseModel
logger = get_logger(__name__)
router = APIRouter(prefix='/api-keys', tags=['api-keys'])
class CreateAPIKeyRequest(BaseModel):
name: str
scopes: List[str]
description: Optional[str] = None
rate_limit: int = 100
expires_at: Optional[str] = None
class UpdateAPIKeyRequest(BaseModel):
name: Optional[str] = None
scopes: Optional[List[str]] = None
description: Optional[str] = None
rate_limit: Optional[int] = None
expires_at: Optional[str] = None
@router.post('/')
async def create_api_key(
key_data: CreateAPIKeyRequest,
current_user: User = Depends(authorize_roles('admin')),
db: Session = Depends(get_db)
):
"""Create a new API key."""
try:
expires_at = None
if key_data.expires_at:
expires_at = datetime.fromisoformat(key_data.expires_at.replace('Z', '+00:00'))
api_key, plain_key = api_key_service.create_api_key(
db=db,
name=key_data.name,
scopes=key_data.scopes,
created_by=current_user.id,
description=key_data.description,
rate_limit=key_data.rate_limit,
expires_at=expires_at
)
return success_response(
data={
'api_key': {
'id': api_key.id,
'name': api_key.name,
'key_prefix': api_key.key_prefix,
'scopes': api_key.scopes,
'rate_limit': api_key.rate_limit,
'expires_at': api_key.expires_at.isoformat() if api_key.expires_at else None
},
'key': plain_key # Return plain key only on creation
},
message='API key created successfully. Save this key securely - it will not be shown again.'
)
except Exception as e:
logger.error(f'Error creating API key: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.get('/')
async def get_api_keys(
current_user: User = Depends(authorize_roles('admin')),
db: Session = Depends(get_db)
):
"""Get all API keys."""
try:
api_keys = db.query(APIKey).order_by(APIKey.created_at.desc()).all()
return success_response(data={
'api_keys': [{
'id': k.id,
'name': k.name,
'key_prefix': k.key_prefix,
'scopes': k.scopes,
'rate_limit': k.rate_limit,
'is_active': k.is_active,
'last_used_at': k.last_used_at.isoformat() if k.last_used_at else None,
'expires_at': k.expires_at.isoformat() if k.expires_at else None,
'created_at': k.created_at.isoformat() if k.created_at else None
} for k in api_keys]
})
except Exception as e:
logger.error(f'Error getting API keys: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.put('/{key_id}')
async def update_api_key(
key_id: int,
key_data: UpdateAPIKeyRequest,
current_user: User = Depends(authorize_roles('admin')),
db: Session = Depends(get_db)
):
"""Update an API key."""
try:
expires_at = None
if key_data.expires_at:
expires_at = datetime.fromisoformat(key_data.expires_at.replace('Z', '+00:00'))
api_key = api_key_service.update_api_key(
db=db,
key_id=key_id,
name=key_data.name,
scopes=key_data.scopes,
description=key_data.description,
rate_limit=key_data.rate_limit,
expires_at=expires_at
)
if not api_key:
raise HTTPException(status_code=404, detail='API key not found')
return success_response(
data={
'api_key': {
'id': api_key.id,
'name': api_key.name,
'key_prefix': api_key.key_prefix,
'scopes': api_key.scopes,
'rate_limit': api_key.rate_limit,
'is_active': api_key.is_active,
'expires_at': api_key.expires_at.isoformat() if api_key.expires_at else None,
'created_at': api_key.created_at.isoformat() if api_key.created_at else None
}
},
message='API key updated successfully'
)
except HTTPException:
raise
except Exception as e:
logger.error(f'Error updating API key: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.delete('/{key_id}')
async def revoke_api_key(
key_id: int,
current_user: User = Depends(authorize_roles('admin')),
db: Session = Depends(get_db)
):
"""Revoke an API key."""
try:
success = api_key_service.revoke_api_key(db=db, key_id=key_id)
if not success:
raise HTTPException(status_code=404, detail='API key not found')
return success_response(message='API key revoked successfully')
except HTTPException:
raise
except Exception as e:
logger.error(f'Error revoking API key: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail=str(e))

View File

@@ -0,0 +1,199 @@
"""
Webhook management routes.
"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List, Optional
from ...shared.config.database import get_db
from ...shared.config.logging_config import get_logger
from ...security.middleware.auth import get_current_user, authorize_roles
from ...auth.models.user import User
from ..services.webhook_service import webhook_service
from ..models.webhook import Webhook, WebhookDelivery, WebhookEventType, WebhookStatus
from ...shared.utils.response_helpers import success_response
from pydantic import BaseModel, HttpUrl
logger = get_logger(__name__)
router = APIRouter(prefix='/webhooks', tags=['webhooks'])
class CreateWebhookRequest(BaseModel):
name: str
url: str
events: List[str]
description: Optional[str] = None
retry_count: int = 3
timeout_seconds: int = 30
@router.post('/')
async def create_webhook(
webhook_data: CreateWebhookRequest,
current_user: User = Depends(authorize_roles('admin')),
db: Session = Depends(get_db)
):
"""Create a new webhook."""
try:
webhook = webhook_service.create_webhook(
db=db,
name=webhook_data.name,
url=webhook_data.url,
events=webhook_data.events,
created_by=current_user.id,
description=webhook_data.description,
retry_count=webhook_data.retry_count,
timeout_seconds=webhook_data.timeout_seconds
)
return success_response(
data={'webhook': {
'id': webhook.id,
'name': webhook.name,
'url': webhook.url,
'events': webhook.events,
'status': webhook.status.value,
'secret': webhook.secret # Return secret only on creation
}},
message='Webhook created successfully'
)
except Exception as e:
logger.error(f'Error creating webhook: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.get('/')
async def get_webhooks(
current_user: User = Depends(authorize_roles('admin')),
db: Session = Depends(get_db)
):
"""Get all webhooks."""
try:
webhooks = db.query(Webhook).order_by(Webhook.created_at.desc()).all()
return success_response(data={
'webhooks': [{
'id': w.id,
'name': w.name,
'url': w.url,
'events': w.events,
'status': w.status.value,
'created_at': w.created_at.isoformat() if w.created_at else None
} for w in webhooks]
})
except Exception as e:
logger.error(f'Error getting webhooks: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.get('/{webhook_id}/deliveries')
async def get_webhook_deliveries(
webhook_id: int,
page: int = Query(1, ge=1),
limit: int = Query(50, ge=1, le=100),
current_user: User = Depends(authorize_roles('admin')),
db: Session = Depends(get_db)
):
"""Get webhook delivery history."""
try:
offset = (page - 1) * limit
deliveries = db.query(WebhookDelivery).filter(
WebhookDelivery.webhook_id == webhook_id
).order_by(WebhookDelivery.created_at.desc()).offset(offset).limit(limit).all()
total = db.query(WebhookDelivery).filter(
WebhookDelivery.webhook_id == webhook_id
).count()
return success_response(data={
'deliveries': [{
'id': d.id,
'event_type': d.event_type,
'status': d.status.value,
'response_status': d.response_status,
'attempt_count': d.attempt_count,
'created_at': d.created_at.isoformat() if d.created_at else None,
'delivered_at': d.delivered_at.isoformat() if d.delivered_at else None
} for d in deliveries],
'pagination': {
'page': page,
'limit': limit,
'total': total,
'total_pages': (total + limit - 1) // limit
}
})
except Exception as e:
logger.error(f'Error getting webhook deliveries: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
class UpdateWebhookRequest(BaseModel):
name: Optional[str] = None
url: Optional[str] = None
events: Optional[List[str]] = None
description: Optional[str] = None
status: Optional[str] = None
retry_count: Optional[int] = None
timeout_seconds: Optional[int] = None
@router.put('/{webhook_id}')
async def update_webhook(
webhook_id: int,
webhook_data: UpdateWebhookRequest,
current_user: User = Depends(authorize_roles('admin')),
db: Session = Depends(get_db)
):
"""Update a webhook."""
try:
status_enum = None
if webhook_data.status:
try:
status_enum = WebhookStatus(webhook_data.status)
except ValueError:
raise HTTPException(status_code=400, detail=f'Invalid status: {webhook_data.status}')
webhook = webhook_service.update_webhook(
db=db,
webhook_id=webhook_id,
name=webhook_data.name,
url=webhook_data.url,
events=webhook_data.events,
description=webhook_data.description,
status=status_enum,
retry_count=webhook_data.retry_count,
timeout_seconds=webhook_data.timeout_seconds
)
if not webhook:
raise HTTPException(status_code=404, detail='Webhook not found')
return success_response(
data={'webhook': {
'id': webhook.id,
'name': webhook.name,
'url': webhook.url,
'events': webhook.events,
'status': webhook.status.value,
'created_at': webhook.created_at.isoformat() if webhook.created_at else None
}},
message='Webhook updated successfully'
)
except HTTPException:
raise
except Exception as e:
logger.error(f'Error updating webhook: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.delete('/{webhook_id}')
async def delete_webhook(
webhook_id: int,
current_user: User = Depends(authorize_roles('admin')),
db: Session = Depends(get_db)
):
"""Delete a webhook."""
try:
success = webhook_service.delete_webhook(db=db, webhook_id=webhook_id)
if not success:
raise HTTPException(status_code=404, detail='Webhook not found')
return success_response(message='Webhook deleted successfully')
except HTTPException:
raise
except Exception as e:
logger.error(f'Error deleting webhook: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail=str(e))