Files
bakery-ia/services/pos/app/api/configurations.py
2025-10-29 06:58:05 +01:00

242 lines
8.8 KiB
Python

"""
POS Configuration API Endpoints
ATOMIC layer - Basic CRUD operations for POS configurations
"""
from fastapi import APIRouter, Depends, HTTPException, Path, Query
from typing import List, Optional, Dict, Any
from uuid import UUID
import structlog
from app.core.database import get_db
from shared.auth.decorators import get_current_user_dep
from shared.auth.access_control import require_user_role, admin_role_required
from shared.routing import RouteBuilder
from shared.security import create_audit_logger, AuditSeverity, AuditAction
from app.services.pos_config_service import POSConfigurationService
from app.schemas.pos_config import POSConfigurationListResponse
from app.models import AuditLog
router = APIRouter()
logger = structlog.get_logger()
audit_logger = create_audit_logger("pos-service", AuditLog)
route_builder = RouteBuilder('pos')
@router.get(
route_builder.build_base_route("configurations"),
response_model=POSConfigurationListResponse
)
@require_user_role(['viewer', 'member', 'admin', 'owner'])
async def list_pos_configurations(
tenant_id: UUID = Path(...),
pos_system: Optional[str] = Query(None),
is_active: Optional[bool] = Query(None),
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=100),
current_user: dict = Depends(get_current_user_dep),
db=Depends(get_db)
):
"""List all POS configurations for a tenant"""
try:
service = POSConfigurationService()
configurations = await service.get_configurations_by_tenant(
tenant_id=tenant_id,
pos_system=pos_system,
is_active=is_active,
skip=skip,
limit=limit
)
total = await service.count_configurations_by_tenant(
tenant_id=tenant_id,
pos_system=pos_system,
is_active=is_active
)
return POSConfigurationListResponse(
configurations=configurations,
total=total,
supported_systems=["square", "toast", "lightspeed"]
)
except Exception as e:
logger.error("Failed to list POS configurations", error=str(e), tenant_id=tenant_id)
raise HTTPException(status_code=500, detail=f"Failed to list configurations: {str(e)}")
@router.post(
route_builder.build_base_route("configurations"),
response_model=dict,
status_code=201
)
@admin_role_required
async def create_pos_configuration(
configuration_data: Dict[str, Any],
tenant_id: UUID = Path(...),
current_user: dict = Depends(get_current_user_dep),
db=Depends(get_db)
):
"""Create a new POS configuration (Admin/Owner only)"""
try:
logger.info("Creating POS configuration",
tenant_id=tenant_id,
pos_system=configuration_data.get("pos_system"),
user_id=current_user.get("user_id"))
return {
"message": "POS configuration created successfully",
"id": "placeholder",
"pos_system": configuration_data.get("pos_system")
}
except Exception as e:
logger.error("Failed to create POS configuration", error=str(e), tenant_id=tenant_id)
raise HTTPException(status_code=500, detail=f"Failed to create configuration: {str(e)}")
@router.get(
route_builder.build_resource_detail_route("configurations", "config_id"),
response_model=dict
)
@require_user_role(['viewer', 'member', 'admin', 'owner'])
async def get_pos_configuration(
tenant_id: UUID = Path(...),
config_id: UUID = Path(...),
current_user: dict = Depends(get_current_user_dep),
db=Depends(get_db)
):
"""Get a specific POS configuration"""
try:
return {
"id": str(config_id),
"tenant_id": str(tenant_id),
"pos_system": "square",
"is_active": True
}
except Exception as e:
logger.error("Failed to get POS configuration", error=str(e),
tenant_id=tenant_id, config_id=config_id)
raise HTTPException(status_code=500, detail=f"Failed to get configuration: {str(e)}")
@router.put(
route_builder.build_resource_detail_route("configurations", "config_id"),
response_model=dict
)
@admin_role_required
async def update_pos_configuration(
configuration_data: Dict[str, Any],
tenant_id: UUID = Path(...),
config_id: UUID = Path(...),
current_user: dict = Depends(get_current_user_dep),
db=Depends(get_db)
):
"""Update a POS configuration (Admin/Owner only)"""
try:
# Log HIGH severity audit event for configuration changes
try:
await audit_logger.log_event(
db_session=db,
tenant_id=str(tenant_id),
user_id=current_user["user_id"],
action=AuditAction.UPDATE.value,
resource_type="pos_configuration",
resource_id=str(config_id),
severity=AuditSeverity.HIGH.value,
description=f"Admin {current_user.get('email', 'unknown')} updated POS configuration",
changes={"configuration_updates": configuration_data},
endpoint=f"/configurations/{config_id}",
method="PUT"
)
except Exception as audit_error:
logger.warning("Failed to log audit event", error=str(audit_error))
logger.info("POS configuration updated",
config_id=str(config_id),
tenant_id=str(tenant_id),
user_id=current_user["user_id"])
return {"message": "Configuration updated successfully", "id": str(config_id)}
except Exception as e:
logger.error("Failed to update POS configuration", error=str(e),
tenant_id=tenant_id, config_id=config_id)
raise HTTPException(status_code=500, detail=f"Failed to update configuration: {str(e)}")
@router.delete(
route_builder.build_resource_detail_route("configurations", "config_id"),
response_model=dict
)
@require_user_role(['owner'])
async def delete_pos_configuration(
tenant_id: UUID = Path(...),
config_id: UUID = Path(...),
current_user: dict = Depends(get_current_user_dep),
db=Depends(get_db)
):
"""Delete a POS configuration (Owner only)"""
try:
# Log CRITICAL severity audit event for configuration deletion
try:
await audit_logger.log_deletion(
db_session=db,
tenant_id=str(tenant_id),
user_id=current_user["user_id"],
resource_type="pos_configuration",
resource_id=str(config_id),
severity=AuditSeverity.CRITICAL.value,
description=f"Owner {current_user.get('email', 'unknown')} deleted POS configuration",
endpoint=f"/configurations/{config_id}",
method="DELETE"
)
except Exception as audit_error:
logger.warning("Failed to log audit event", error=str(audit_error))
logger.info("POS configuration deleted",
config_id=str(config_id),
tenant_id=str(tenant_id),
user_id=current_user["user_id"])
return {"message": "Configuration deleted successfully"}
except Exception as e:
logger.error("Failed to delete POS configuration", error=str(e),
tenant_id=tenant_id, config_id=config_id)
raise HTTPException(status_code=500, detail=f"Failed to delete configuration: {str(e)}")
# ============================================================================
# Reference Data
# ============================================================================
@router.get(
route_builder.build_global_route("supported-systems"),
response_model=dict
)
async def get_supported_pos_systems():
"""Get list of supported POS systems (no tenant context required)"""
return {
"systems": [
{
"id": "square",
"name": "Square POS",
"description": "Square Point of Sale system",
"features": ["payments", "inventory", "analytics", "webhooks"],
"supported_regions": ["US", "CA", "AU", "JP", "GB", "IE", "ES", "FR"]
},
{
"id": "toast",
"name": "Toast POS",
"description": "Toast restaurant POS system",
"features": ["orders", "payments", "menu_management", "webhooks"],
"supported_regions": ["US", "CA", "IE", "ES"]
},
{
"id": "lightspeed",
"name": "Lightspeed Restaurant",
"description": "Lightspeed restaurant management system",
"features": ["orders", "inventory", "reservations", "webhooks"],
"supported_regions": ["US", "CA", "EU", "AU"]
}
]
}