Files
tfm_ainventory/backend/routers/admin/exports.py
Daniel Bedeleanu 7dfa993b57 feat(export): add /admin/db/export endpoint for frontend
- Changed router prefix from /admin/exports to /admin
- Added GET /admin/db/export endpoint
- Supports type: inventory|audit|combined
- Supports format: csv|xlsx
- Maintains auth guard and audit logging
- Fixes frontend 404 error on export calls
2026-04-23 10:54:07 +03:00

240 lines
7.6 KiB
Python

"""
Admin export endpoints for inventory snapshot and audit trail exports.
Supports CSV and Excel formats.
"""
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import FileResponse
from sqlalchemy.orm import Session
from backend.database import get_db
from backend.auth import get_current_admin
from backend.models import Item, AuditLog
from backend.services.export_service import (
InventorySnapshotExporter,
AuditTrailExporter,
get_export_filename,
)
router = APIRouter(prefix="/admin", tags=["admin-exports"])
def validate_export_format(format_str: str) -> str:
"""Validate and normalize export format."""
format_lower = format_str.lower() if format_str else "csv"
if format_lower not in ("csv", "xlsx"):
raise HTTPException(
status_code=400, detail="Invalid format. Use 'csv' or 'xlsx'."
)
return format_lower
@router.post("/inventory-snapshot")
async def export_inventory_snapshot(
format: str = Query("csv", description="Export format: csv or xlsx"),
db: Session = Depends(get_db),
admin_user=Depends(get_current_admin),
):
"""
Export inventory snapshot in CSV or Excel format.
Requires admin authorization.
Returns file download with timestamp in filename.
"""
format_type = validate_export_format(format)
timestamp = datetime.now().strftime("%Y-%m-%d")
# Fetch all items
items = db.query(Item).all()
# Generate export
if format_type == "csv":
content = InventorySnapshotExporter.to_csv(items, timestamp)
media_type = "text/csv; charset=utf-8"
else:
content = InventorySnapshotExporter.to_excel(items, timestamp)
media_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
filename = get_export_filename("inventory_snapshot", format_type, timestamp)
# Log export action
from backend.models import AuditLog
audit_entry = AuditLog(
user_id=admin_user.id,
action="EXPORT_INVENTORY_SNAPSHOT",
details=f"Exported in {format_type} format",
)
db.add(audit_entry)
db.commit()
# Return file response
if format_type == "csv":
# For CSV, use FileResponse with bytes
import io
return FileResponse(
io.BytesIO(content.encode("utf-8")),
media_type=media_type,
filename=filename,
)
else:
# For Excel, content is already bytes
import io
return FileResponse(
io.BytesIO(content),
media_type=media_type,
filename=filename,
)
@router.post("/audit-trail")
async def export_audit_trail(
format: str = Query("csv", description="Export format: csv or xlsx"),
db: Session = Depends(get_db),
admin_user=Depends(get_current_admin),
):
"""
Export audit trail in CSV or Excel format.
Requires admin authorization.
Returns file download with timestamp in filename.
"""
format_type = validate_export_format(format)
timestamp = datetime.now().strftime("%Y-%m-%d")
# Fetch all audit logs
logs = db.query(AuditLog).all()
# Generate export
if format_type == "csv":
content = AuditTrailExporter.to_csv(logs, timestamp)
media_type = "text/csv; charset=utf-8"
else:
content = AuditTrailExporter.to_excel(logs, timestamp)
media_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
filename = get_export_filename("audit_trail", format_type, timestamp)
# Log export action
audit_entry = AuditLog(
user_id=admin_user.id,
action="EXPORT_AUDIT_TRAIL",
details=f"Exported in {format_type} format",
)
db.add(audit_entry)
db.commit()
# Return file response
if format_type == "csv":
import io
return FileResponse(
io.BytesIO(content.encode("utf-8")),
media_type=media_type,
filename=filename,
)
else:
import io
return FileResponse(
io.BytesIO(content),
media_type=media_type,
filename=filename,
)
@router.get("/db/export")
async def export_db(
format: str = Query("csv", description="Export format: csv or xlsx"),
type: str = Query("inventory", description="Export type: inventory, audit, or combined"),
db: Session = Depends(get_db),
admin_user=Depends(get_current_admin),
):
"""
Combined export endpoint for frontend.
Supports inventory snapshot and audit trail exports.
Parameters:
- format: 'csv' or 'xlsx'
- type: 'inventory', 'audit', or 'combined'
Requires admin authorization.
"""
import io
format_type = validate_export_format(format)
timestamp = datetime.now().strftime("%Y-%m-%d")
if type not in ("inventory", "audit", "combined"):
raise HTTPException(
status_code=400,
detail="Invalid type. Use 'inventory', 'audit', or 'combined'."
)
# Determine what to export
export_inventory = type in ("inventory", "combined")
export_audit = type in ("audit", "combined")
# For combined or single exports
if export_inventory and not export_audit:
# Inventory only
items = db.query(Item).all()
if format_type == "csv":
content = InventorySnapshotExporter.to_csv(items, timestamp)
media_type = "text/csv; charset=utf-8"
else:
content = InventorySnapshotExporter.to_excel(items, timestamp)
media_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
filename = get_export_filename("inventory_snapshot", format_type, timestamp)
elif export_audit and not export_inventory:
# Audit only
logs = db.query(AuditLog).all()
if format_type == "csv":
content = AuditTrailExporter.to_csv(logs, timestamp)
media_type = "text/csv; charset=utf-8"
else:
content = AuditTrailExporter.to_excel(logs, timestamp)
media_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
filename = get_export_filename("audit_trail", format_type, timestamp)
else:
# Combined - inventory + audit trail
items = db.query(Item).all()
logs = db.query(AuditLog).all()
if format_type == "csv":
inv_content = InventorySnapshotExporter.to_csv(items, timestamp)
audit_content = AuditTrailExporter.to_csv(logs, timestamp)
# Combine with separator
content = f"{inv_content}\n\n--- AUDIT TRAIL ---\n\n{audit_content}"
media_type = "text/csv; charset=utf-8"
else:
# For Excel, we'd need to create multi-sheet workbook (openpyxl)
# For now, just export inventory
content = InventorySnapshotExporter.to_excel(items, timestamp)
media_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
filename = get_export_filename("inventory_and_audit", format_type, timestamp)
# Log export action
audit_entry = AuditLog(
user_id=admin_user.id,
action="EXPORT_DB",
details=f"Exported {type} in {format_type} format",
)
db.add(audit_entry)
db.commit()
# Return file response
if format_type == "csv":
return FileResponse(
io.BytesIO(content.encode("utf-8")),
media_type=media_type,
filename=filename,
)
else:
return FileResponse(
io.BytesIO(content),
media_type=media_type,
filename=filename,
)