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

@@ -16,9 +16,13 @@ RUN apt-get update && apt-get install -y \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements
COPY shared/requirements-tracing.txt /tmp/
COPY services/production/requirements.txt .
# Install Python dependencies
RUN pip install --no-cache-dir -r /tmp/requirements-tracing.txt
RUN pip install --no-cache-dir -r requirements.txt
# Copy shared libraries from the shared stage

View File

@@ -10,7 +10,9 @@ from uuid import UUID
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.production_service import ProductionService
from app.schemas.production import (
@@ -27,6 +29,9 @@ logger = structlog.get_logger()
route_builder = RouteBuilder('production')
router = APIRouter(tags=["production-batches"])
# Initialize audit logger
audit_logger = create_audit_logger("production-service")
def get_production_service() -> ProductionService:
"""Dependency injection for production service"""
@@ -229,16 +234,33 @@ async def update_production_batch(
@router.delete(
route_builder.build_resource_detail_route("batches", "batch_id")
)
@require_user_role(['admin', 'owner'])
async def delete_production_batch(
tenant_id: UUID = Path(...),
batch_id: UUID = Path(...),
current_user: dict = Depends(get_current_user_dep),
production_service: ProductionService = Depends(get_production_service)
):
"""Cancel/delete draft batch (soft delete preferred)"""
"""Cancel/delete draft batch (Admin+ only, soft delete preferred)"""
try:
await production_service.delete_production_batch(tenant_id, batch_id)
# Log audit event for batch deletion
try:
db = next(get_db())
await audit_logger.log_deletion(
db_session=db,
tenant_id=str(tenant_id),
user_id=current_user["user_id"],
resource_type="production_batch",
resource_id=str(batch_id),
description=f"Deleted production batch",
endpoint=f"/batches/{batch_id}",
method="DELETE"
)
except Exception as audit_error:
logger.warning("Failed to log audit event", error=str(audit_error))
logger.info("Deleted production batch",
batch_id=str(batch_id), tenant_id=str(tenant_id))

View File

@@ -10,7 +10,9 @@ from uuid import UUID
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.production_service import ProductionService
from app.schemas.production import (
@@ -24,6 +26,9 @@ logger = structlog.get_logger()
route_builder = RouteBuilder('production')
router = APIRouter(tags=["production-schedules"])
# Initialize audit logger
audit_logger = create_audit_logger("production-service")
def get_production_service() -> ProductionService:
"""Dependency injection for production service"""
@@ -125,13 +130,14 @@ async def get_production_schedule_details(
route_builder.build_base_route("schedules"),
response_model=ProductionScheduleResponse
)
@require_user_role(['admin', 'owner'])
async def create_production_schedule(
schedule_data: ProductionScheduleCreate,
tenant_id: UUID = Path(...),
current_user: dict = Depends(get_current_user_dep),
production_service: ProductionService = Depends(get_production_service)
):
"""Generate or manually create a daily/shift schedule"""
"""Generate or manually create a daily/shift schedule (Admin+ only)"""
try:
schedule = await production_service.create_production_schedule(tenant_id, schedule_data)
@@ -153,6 +159,7 @@ async def create_production_schedule(
route_builder.build_resource_detail_route("schedules", "schedule_id"),
response_model=ProductionScheduleResponse
)
@require_user_role(['admin', 'owner'])
async def update_production_schedule(
schedule_update: ProductionScheduleUpdate,
tenant_id: UUID = Path(...),
@@ -160,7 +167,7 @@ async def update_production_schedule(
current_user: dict = Depends(get_current_user_dep),
production_service: ProductionService = Depends(get_production_service)
):
"""Edit schedule before finalizing"""
"""Edit schedule before finalizing (Admin+ only)"""
try:
schedule = await production_service.update_production_schedule(tenant_id, schedule_id, schedule_update)

View File

@@ -5,6 +5,13 @@
Production service models
"""
# 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)
from .production import (
ProductionBatch,
ProductionSchedule,
@@ -31,4 +38,5 @@ __all__ = [
"EquipmentStatus",
"ProcessStage",
"EquipmentType",
"AuditLog",
]

View File

@@ -1,18 +1,18 @@
"""initial_schema_20251009_2039
"""initial_schema_20251015_1231
Revision ID: ff7cc8350951
Revision ID: 42a9c1fd8fec
Revises:
Create Date: 2025-10-09 20:39:57.570220+02:00
Create Date: 2025-10-15 12:31:07.740405+02:00
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = 'ff7cc8350951'
revision: str = '42a9c1fd8fec'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
@@ -20,6 +20,38 @@ depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('audit_logs',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('user_id', sa.UUID(), nullable=False),
sa.Column('action', sa.String(length=100), nullable=False),
sa.Column('resource_type', sa.String(length=100), nullable=False),
sa.Column('resource_id', sa.String(length=255), nullable=True),
sa.Column('severity', sa.String(length=20), nullable=False),
sa.Column('service_name', sa.String(length=100), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('changes', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('audit_metadata', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('ip_address', sa.String(length=45), nullable=True),
sa.Column('user_agent', sa.Text(), nullable=True),
sa.Column('endpoint', sa.String(length=255), nullable=True),
sa.Column('method', sa.String(length=10), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index('idx_audit_resource_type_action', 'audit_logs', ['resource_type', 'action'], unique=False)
op.create_index('idx_audit_service_created', 'audit_logs', ['service_name', 'created_at'], unique=False)
op.create_index('idx_audit_severity_created', 'audit_logs', ['severity', 'created_at'], unique=False)
op.create_index('idx_audit_tenant_created', 'audit_logs', ['tenant_id', 'created_at'], unique=False)
op.create_index('idx_audit_user_created', 'audit_logs', ['user_id', 'created_at'], unique=False)
op.create_index(op.f('ix_audit_logs_action'), 'audit_logs', ['action'], unique=False)
op.create_index(op.f('ix_audit_logs_created_at'), 'audit_logs', ['created_at'], unique=False)
op.create_index(op.f('ix_audit_logs_resource_id'), 'audit_logs', ['resource_id'], unique=False)
op.create_index(op.f('ix_audit_logs_resource_type'), 'audit_logs', ['resource_type'], unique=False)
op.create_index(op.f('ix_audit_logs_service_name'), 'audit_logs', ['service_name'], unique=False)
op.create_index(op.f('ix_audit_logs_severity'), 'audit_logs', ['severity'], unique=False)
op.create_index(op.f('ix_audit_logs_tenant_id'), 'audit_logs', ['tenant_id'], unique=False)
op.create_index(op.f('ix_audit_logs_user_id'), 'audit_logs', ['user_id'], unique=False)
op.create_table('equipment',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('tenant_id', sa.UUID(), nullable=False),
@@ -255,4 +287,18 @@ def downgrade() -> None:
op.drop_table('production_batches')
op.drop_index(op.f('ix_equipment_tenant_id'), table_name='equipment')
op.drop_table('equipment')
op.drop_index(op.f('ix_audit_logs_user_id'), table_name='audit_logs')
op.drop_index(op.f('ix_audit_logs_tenant_id'), table_name='audit_logs')
op.drop_index(op.f('ix_audit_logs_severity'), table_name='audit_logs')
op.drop_index(op.f('ix_audit_logs_service_name'), table_name='audit_logs')
op.drop_index(op.f('ix_audit_logs_resource_type'), table_name='audit_logs')
op.drop_index(op.f('ix_audit_logs_resource_id'), table_name='audit_logs')
op.drop_index(op.f('ix_audit_logs_created_at'), table_name='audit_logs')
op.drop_index(op.f('ix_audit_logs_action'), table_name='audit_logs')
op.drop_index('idx_audit_user_created', table_name='audit_logs')
op.drop_index('idx_audit_tenant_created', table_name='audit_logs')
op.drop_index('idx_audit_severity_created', table_name='audit_logs')
op.drop_index('idx_audit_service_created', table_name='audit_logs')
op.drop_index('idx_audit_resource_type_action', table_name='audit_logs')
op.drop_table('audit_logs')
# ### end Alembic commands ###