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
This commit is contained in:
@@ -17,7 +17,7 @@ from backend.services.export_service import (
|
||||
get_export_filename,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/admin/exports", tags=["admin-exports"])
|
||||
router = APIRouter(prefix="/admin", tags=["admin-exports"])
|
||||
|
||||
|
||||
def validate_export_format(format_str: str) -> str:
|
||||
@@ -139,3 +139,101 @@ async def export_audit_trail(
|
||||
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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user