Add role-based filtering and imporve code

This commit is contained in:
Urtzi Alfaro
2025-10-15 16:12:49 +02:00
parent 96ad5c6692
commit 8f9e9a7edc
158 changed files with 11033 additions and 1544 deletions

View File

@@ -13,6 +13,7 @@ import structlog
from shared.auth.decorators import get_current_user_dep
from shared.auth.access_control import require_user_role
from shared.routing import RouteBuilder
from shared.security import create_audit_logger, AuditSeverity, AuditAction
from app.core.database import get_db
from app.services.orders_service import OrdersService
from app.schemas.order_schemas import (
@@ -22,6 +23,7 @@ from app.schemas.order_schemas import (
)
logger = structlog.get_logger()
audit_logger = create_audit_logger("orders-service")
# Create route builder for consistent URL structure
route_builder = RouteBuilder('orders')
@@ -236,7 +238,10 @@ async def delete_customer(
orders_service: OrdersService = Depends(get_orders_service),
db = Depends(get_db)
):
"""Delete a customer (soft delete)"""
"""
Delete a customer (Admin+ only, GDPR-compliant soft delete)
Removes PII while maintaining referential integrity
"""
try:
customer = await orders_service.customer_repo.get(db, customer_id, tenant_id)
if not customer:
@@ -245,10 +250,39 @@ async def delete_customer(
detail="Customer not found"
)
# Capture customer data before deletion (for audit trail)
# Note: This is anonymized after retention period in compliance with GDPR
customer_data = {
"customer_code": customer.customer_code,
"customer_name": customer.customer_name,
"email": customer.email,
"phone": customer.phone,
"business_type": customer.business_type if hasattr(customer, 'business_type') else None
}
await orders_service.customer_repo.delete(db, customer_id, tenant_id)
logger.info("Customer deleted successfully",
customer_id=str(customer_id))
# Log HIGH severity audit event for customer deletion (GDPR compliance)
try:
await audit_logger.log_deletion(
db_session=db,
tenant_id=str(tenant_id),
user_id=current_user["user_id"],
resource_type="customer",
resource_id=str(customer_id),
resource_data=customer_data,
description=f"Admin {current_user.get('email', 'unknown')} deleted customer {customer_data['customer_code']} (GDPR-compliant soft delete)",
endpoint=f"/customers/{customer_id}",
method="DELETE",
severity=AuditSeverity.HIGH.value
)
except Exception as audit_error:
logger.warning("Failed to log audit event", error=str(audit_error))
logger.info("Customer deleted successfully (GDPR-compliant)",
customer_id=str(customer_id),
tenant_id=str(tenant_id),
user_id=current_user["user_id"])
except HTTPException:
raise

View File

@@ -14,6 +14,7 @@ import structlog
from shared.auth.decorators import get_current_user_dep
from shared.auth.access_control import require_user_role
from shared.routing import RouteBuilder
from shared.security import create_audit_logger, AuditSeverity, AuditAction
from app.core.database import get_db
from app.services.orders_service import OrdersService
from app.schemas.order_schemas import (
@@ -23,6 +24,7 @@ from app.schemas.order_schemas import (
)
logger = structlog.get_logger()
audit_logger = create_audit_logger("orders-service")
# Create route builder for consistent URL structure
route_builder = RouteBuilder('orders')
@@ -238,7 +240,7 @@ async def delete_order(
orders_service: OrdersService = Depends(get_orders_service),
db = Depends(get_db)
):
"""Delete an order (soft delete)"""
"""Delete an order (Admin+ only, soft delete)"""
try:
order = await orders_service.order_repo.get(db, order_id, tenant_id)
if not order:
@@ -247,10 +249,37 @@ async def delete_order(
detail="Order not found"
)
# Capture order data before deletion
order_data = {
"order_number": order.order_number,
"customer_id": str(order.customer_id) if order.customer_id else None,
"order_status": order.order_status,
"total_amount": float(order.total_amount) if order.total_amount else 0.0,
"order_date": order.order_date.isoformat() if order.order_date else None
}
await orders_service.order_repo.delete(db, order_id, tenant_id)
# Log audit event for order deletion
try:
await audit_logger.log_deletion(
db_session=db,
tenant_id=str(tenant_id),
user_id=current_user["user_id"],
resource_type="order",
resource_id=str(order_id),
resource_data=order_data,
description=f"Admin {current_user.get('email', 'unknown')} deleted order {order_data['order_number']}",
endpoint=f"/orders/{order_id}",
method="DELETE"
)
except Exception as audit_error:
logger.warning("Failed to log audit event", error=str(audit_error))
logger.info("Order deleted successfully",
order_id=str(order_id))
order_id=str(order_id),
tenant_id=str(tenant_id),
user_id=current_user["user_id"])
except HTTPException:
raise

View File

@@ -4,6 +4,13 @@ Orders Service Models Package
Import all models to ensure they are registered with SQLAlchemy Base.
"""
# Import AuditLog model for this service
from shared.security import create_audit_log_model
from shared.database.base import Base
# Create audit log model for this service
AuditLog = create_audit_log_model(Base)
# Import all models to register them with the Base metadata
from .customer import Customer, CustomerContact
from .order import CustomerOrder, OrderItem, OrderStatusHistory
@@ -60,4 +67,5 @@ __all__ = [
"PriorityLevel",
"RequirementStatus",
"RiskLevel",
"AuditLog",
]

View File

@@ -9,9 +9,9 @@ import json
import uuid
from datetime import datetime, date, timedelta
from typing import Optional, Dict, Any, List
import redis
import structlog
from pydantic import BaseModel
from shared.redis_utils import get_redis_client
from app.core.config import settings
from app.models.procurement import ProcurementPlan
@@ -22,31 +22,17 @@ logger = structlog.get_logger()
class CacheService:
"""Service for managing Redis cache operations"""
def __init__(self, redis_url: Optional[str] = None):
"""Initialize Redis connection"""
self.redis_url = redis_url or settings.REDIS_URL
def __init__(self):
"""Initialize cache service"""
self._redis_client = None
self._connect()
def _connect(self):
"""Connect to Redis"""
try:
self._redis_client = redis.from_url(
self.redis_url,
decode_responses=True,
socket_keepalive=True,
socket_keepalive_options={1: 1, 3: 3, 5: 5}, # Use integer keys
retry_on_timeout=True,
max_connections=50
)
# Test connection
self._redis_client.ping()
logger.info("Redis connection established")
except Exception as e:
logger.error("Failed to connect to Redis", error=str(e))
self._redis_client = None
async def _get_redis(self):
"""Get shared Redis client"""
if self._redis_client is None:
self._redis_client = await get_redis_client()
return self._redis_client
@property
def redis(self):
"""Get Redis client with connection check"""