Improve the frontend 5

This commit is contained in:
Urtzi Alfaro
2025-11-02 20:24:44 +01:00
parent 0220da1725
commit 5adb0e39c0
90 changed files with 10658 additions and 2548 deletions

View File

@@ -0,0 +1,90 @@
#!/usr/bin/env python3
"""
Script to complete audit router registration in all remaining services.
"""
import re
from pathlib import Path
BASE_DIR = Path(__file__).parent.parent / "services"
# Services that still need updates (suppliers, pos, training, notification, external, forecasting)
SERVICES = ['suppliers', 'pos', 'training', 'notification', 'external', 'forecasting']
def update_service(service_name):
main_file = BASE_DIR / service_name / "app" / "main.py"
if not main_file.exists():
print(f"⚠️ {service_name}: main.py not found")
return False
content = main_file.read_text()
modified = False
# Check if audit is already imported
if 'import.*audit' in content or ', audit' in content:
print(f"{service_name}: audit already imported")
else:
# Add audit import - find the from .api or from app.api import line
patterns = [
(r'(from \.api import [^)]+)(\))', r'\1, audit\2'), # Multi-line with parentheses
(r'(from \.api import .+)', r'\1, audit'), # Single line with .api
(r'(from app\.api import [^)]+)(\))', r'\1, audit\2'), # Multi-line with app.api
(r'(from app\.api import .+)', r'\1, audit'), # Single line with app.api
]
for pattern, replacement in patterns:
new_content = re.sub(pattern, replacement, content)
if new_content != content:
content = new_content
modified = True
print(f"{service_name}: added audit import")
break
if not modified:
print(f"⚠️ {service_name}: could not find import pattern, needs manual update")
return False
# Check if audit router is already registered
if 'service.add_router(audit.router)' in content:
print(f"{service_name}: audit router already registered")
else:
# Find the last service.add_router line and add audit router after it
lines = content.split('\n')
last_router_index = -1
for i, line in enumerate(lines):
if 'service.add_router(' in line and 'audit' not in line:
last_router_index = i
if last_router_index != -1:
# Insert audit router after the last router registration
lines.insert(last_router_index + 1, 'service.add_router(audit.router)')
content = '\n'.join(lines)
modified = True
print(f"{service_name}: added audit router registration")
else:
print(f"⚠️ {service_name}: could not find router registration pattern, needs manual update")
return False
if modified:
main_file.write_text(content)
print(f"{service_name}: updated successfully")
else:
print(f" {service_name}: no changes needed")
return True
def main():
print("Completing audit router registration in remaining services...\n")
success_count = 0
for service in SERVICES:
if update_service(service):
success_count += 1
print()
print(f"\nCompleted: {success_count}/{len(SERVICES)} services updated successfully")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,281 @@
#!/usr/bin/env python3
"""
Script to generate audit.py endpoint files for all services.
This ensures consistency across all microservices.
"""
import os
from pathlib import Path
# Template for audit.py file
AUDIT_TEMPLATE = """# services/{service}/app/api/audit.py
\"\"\"
Audit Logs API - Retrieve audit trail for {service} service
\"\"\"
from fastapi import APIRouter, Depends, HTTPException, Query, Path, status
from typing import Optional, Dict, Any
from uuid import UUID
from datetime import datetime
import structlog
from sqlalchemy import select, func, and_
from sqlalchemy.ext.asyncio import AsyncSession
from app.models import AuditLog
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.models.audit_log_schemas import (
AuditLogResponse,
AuditLogListResponse,
AuditLogStatsResponse
)
from app.core.database import database_manager
route_builder = RouteBuilder('{service_route}')
router = APIRouter(tags=["audit-logs"])
logger = structlog.get_logger()
async def get_db():
\"\"\"Database session dependency\"\"\"
async with database_manager.get_session() as session:
yield session
@router.get(
route_builder.build_base_route("audit-logs"),
response_model=AuditLogListResponse
)
@require_user_role(['admin', 'owner'])
async def get_audit_logs(
tenant_id: UUID = Path(..., description="Tenant ID"),
start_date: Optional[datetime] = Query(None, description="Filter logs from this date"),
end_date: Optional[datetime] = Query(None, description="Filter logs until this date"),
user_id: Optional[UUID] = Query(None, description="Filter by user ID"),
action: Optional[str] = Query(None, description="Filter by action type"),
resource_type: Optional[str] = Query(None, description="Filter by resource type"),
severity: Optional[str] = Query(None, description="Filter by severity level"),
search: Optional[str] = Query(None, description="Search in description field"),
limit: int = Query(100, ge=1, le=1000, description="Number of records to return"),
offset: int = Query(0, ge=0, description="Number of records to skip"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
\"\"\"
Get audit logs for {service} service.
Requires admin or owner role.
\"\"\"
try:
logger.info(
"Retrieving audit logs",
tenant_id=tenant_id,
user_id=current_user.get("user_id"),
filters={{
"start_date": start_date,
"end_date": end_date,
"action": action,
"resource_type": resource_type,
"severity": severity
}}
)
# Build query filters
filters = [AuditLog.tenant_id == tenant_id]
if start_date:
filters.append(AuditLog.created_at >= start_date)
if end_date:
filters.append(AuditLog.created_at <= end_date)
if user_id:
filters.append(AuditLog.user_id == user_id)
if action:
filters.append(AuditLog.action == action)
if resource_type:
filters.append(AuditLog.resource_type == resource_type)
if severity:
filters.append(AuditLog.severity == severity)
if search:
filters.append(AuditLog.description.ilike(f"%{{search}}%"))
# Count total matching records
count_query = select(func.count()).select_from(AuditLog).where(and_(*filters))
total_result = await db.execute(count_query)
total = total_result.scalar() or 0
# Fetch paginated results
query = (
select(AuditLog)
.where(and_(*filters))
.order_by(AuditLog.created_at.desc())
.limit(limit)
.offset(offset)
)
result = await db.execute(query)
audit_logs = result.scalars().all()
# Convert to response models
items = [AuditLogResponse.from_orm(log) for log in audit_logs]
logger.info(
"Successfully retrieved audit logs",
tenant_id=tenant_id,
total=total,
returned=len(items)
)
return AuditLogListResponse(
items=items,
total=total,
limit=limit,
offset=offset,
has_more=(offset + len(items)) < total
)
except Exception as e:
logger.error(
"Failed to retrieve audit logs",
error=str(e),
tenant_id=tenant_id
)
raise HTTPException(
status_code=500,
detail=f"Failed to retrieve audit logs: {{str(e)}}"
)
@router.get(
route_builder.build_base_route("audit-logs/stats"),
response_model=AuditLogStatsResponse
)
@require_user_role(['admin', 'owner'])
async def get_audit_log_stats(
tenant_id: UUID = Path(..., description="Tenant ID"),
start_date: Optional[datetime] = Query(None, description="Filter logs from this date"),
end_date: Optional[datetime] = Query(None, description="Filter logs until this date"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
\"\"\"
Get audit log statistics for {service} service.
Requires admin or owner role.
\"\"\"
try:
logger.info(
"Retrieving audit log statistics",
tenant_id=tenant_id,
user_id=current_user.get("user_id")
)
# Build base filters
filters = [AuditLog.tenant_id == tenant_id]
if start_date:
filters.append(AuditLog.created_at >= start_date)
if end_date:
filters.append(AuditLog.created_at <= end_date)
# Total events
count_query = select(func.count()).select_from(AuditLog).where(and_(*filters))
total_result = await db.execute(count_query)
total_events = total_result.scalar() or 0
# Events by action
action_query = (
select(AuditLog.action, func.count().label('count'))
.where(and_(*filters))
.group_by(AuditLog.action)
)
action_result = await db.execute(action_query)
events_by_action = {{row.action: row.count for row in action_result}}
# Events by severity
severity_query = (
select(AuditLog.severity, func.count().label('count'))
.where(and_(*filters))
.group_by(AuditLog.severity)
)
severity_result = await db.execute(severity_query)
events_by_severity = {{row.severity: row.count for row in severity_result}}
# Events by resource type
resource_query = (
select(AuditLog.resource_type, func.count().label('count'))
.where(and_(*filters))
.group_by(AuditLog.resource_type)
)
resource_result = await db.execute(resource_query)
events_by_resource_type = {{row.resource_type: row.count for row in resource_result}}
# Date range
date_range_query = (
select(
func.min(AuditLog.created_at).label('min_date'),
func.max(AuditLog.created_at).label('max_date')
)
.where(and_(*filters))
)
date_result = await db.execute(date_range_query)
date_row = date_result.one()
logger.info(
"Successfully retrieved audit log statistics",
tenant_id=tenant_id,
total_events=total_events
)
return AuditLogStatsResponse(
total_events=total_events,
events_by_action=events_by_action,
events_by_severity=events_by_severity,
events_by_resource_type=events_by_resource_type,
date_range={{
"min": date_row.min_date,
"max": date_row.max_date
}}
)
except Exception as e:
logger.error(
"Failed to retrieve audit log statistics",
error=str(e),
tenant_id=tenant_id
)
raise HTTPException(
status_code=500,
detail=f"Failed to retrieve audit log statistics: {{str(e)}}"
)
"""
# Services to generate for (excluding sales and inventory which are already done)
SERVICES = [
('orders', 'orders'),
('production', 'production'),
('recipes', 'recipes'),
('suppliers', 'suppliers'),
('pos', 'pos'),
('training', 'training'),
('notification', 'notification'),
('external', 'external'),
('forecasting', 'forecasting'),
]
def main():
base_path = Path(__file__).parent.parent / "services"
for service_name, route_name in SERVICES:
service_path = base_path / service_name / "app" / "api"
audit_file = service_path / "audit.py"
# Create the file
content = AUDIT_TEMPLATE.format(
service=service_name,
service_route=route_name
)
audit_file.write_text(content)
print(f"✓ Created {audit_file}")
if __name__ == "__main__":
main()
print("\n✓ All audit endpoint files generated successfully!")

View File

@@ -0,0 +1,41 @@
#!/bin/bash
# Script to register audit routers in all service main.py files
set -e
BASE_DIR="/Users/urtzialfaro/Documents/bakery-ia/services"
echo "Registering audit routers in service main.py files..."
# Function to add audit import and router registration
add_audit_to_service() {
local service=$1
local main_file="$BASE_DIR/$service/app/main.py"
if [ ! -f "$main_file" ]; then
echo "⚠️ $service: main.py not found, skipping"
return
fi
# Check if audit is already imported
if grep -q "import.*audit" "$main_file"; then
echo "$service: audit already imported"
else
echo "⚠️ $service: needs manual import addition"
fi
# Check if audit router is already registered
if grep -q "service.add_router(audit.router)" "$main_file"; then
echo "$service: audit router already registered"
else
echo "⚠️ $service: needs manual router registration"
fi
}
# Process each service
for service in recipes suppliers pos training notification external forecasting; do
add_audit_to_service "$service"
done
echo ""
echo "Done! Please check warnings above for services needing manual updates."