print_log.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import logging
  2. from datetime import datetime
  3. from fastapi import APIRouter, Depends, HTTPException, Query
  4. from fastapi.responses import FileResponse
  5. from sqlalchemy import delete, func, select
  6. from sqlalchemy.ext.asyncio import AsyncSession
  7. from backend.app.core.auth import RequirePermissionIfAuthEnabled
  8. from backend.app.core.config import settings
  9. from backend.app.core.database import get_db
  10. from backend.app.core.permissions import Permission
  11. from backend.app.models.print_log import PrintLogEntry
  12. from backend.app.models.user import User
  13. from backend.app.schemas.print_log import PrintLogEntrySchema, PrintLogResponse
  14. logger = logging.getLogger(__name__)
  15. router = APIRouter(prefix="/print-log", tags=["print-log"])
  16. @router.get("/", response_model=PrintLogResponse)
  17. async def get_print_log(
  18. search: str | None = None,
  19. printer_id: int | None = None,
  20. created_by_username: str | None = None,
  21. status: str | None = None,
  22. date_from: datetime | None = None,
  23. date_to: datetime | None = None,
  24. limit: int = Query(default=50, ge=1, le=500),
  25. offset: int = Query(default=0, ge=0),
  26. db: AsyncSession = Depends(get_db),
  27. _: User | None = RequirePermissionIfAuthEnabled(Permission.ARCHIVES_READ),
  28. ):
  29. """Get the print log."""
  30. query = select(PrintLogEntry)
  31. count_query = select(func.count(PrintLogEntry.id))
  32. if printer_id is not None:
  33. query = query.where(PrintLogEntry.printer_id == printer_id)
  34. count_query = count_query.where(PrintLogEntry.printer_id == printer_id)
  35. if created_by_username:
  36. query = query.where(PrintLogEntry.created_by_username == created_by_username)
  37. count_query = count_query.where(PrintLogEntry.created_by_username == created_by_username)
  38. if status:
  39. query = query.where(PrintLogEntry.status == status)
  40. count_query = count_query.where(PrintLogEntry.status == status)
  41. if search:
  42. query = query.where(PrintLogEntry.print_name.ilike(f"%{search}%"))
  43. count_query = count_query.where(PrintLogEntry.print_name.ilike(f"%{search}%"))
  44. if date_from:
  45. query = query.where(PrintLogEntry.created_at >= date_from)
  46. count_query = count_query.where(PrintLogEntry.created_at >= date_from)
  47. if date_to:
  48. query = query.where(PrintLogEntry.created_at <= date_to)
  49. count_query = count_query.where(PrintLogEntry.created_at <= date_to)
  50. # Get total count
  51. total_result = await db.execute(count_query)
  52. total = total_result.scalar() or 0
  53. # Get paginated results
  54. query = query.order_by(PrintLogEntry.created_at.desc()).offset(offset).limit(limit)
  55. result = await db.execute(query)
  56. entries = result.scalars().all()
  57. return PrintLogResponse(
  58. items=[
  59. PrintLogEntrySchema(
  60. id=e.id,
  61. print_name=e.print_name,
  62. printer_name=e.printer_name,
  63. printer_id=e.printer_id,
  64. status=e.status,
  65. started_at=e.started_at,
  66. completed_at=e.completed_at,
  67. duration_seconds=e.duration_seconds,
  68. filament_type=e.filament_type,
  69. filament_color=e.filament_color,
  70. filament_used_grams=e.filament_used_grams,
  71. thumbnail_path=e.thumbnail_path,
  72. created_by_username=e.created_by_username,
  73. created_at=e.created_at,
  74. )
  75. for e in entries
  76. ],
  77. total=total,
  78. )
  79. @router.get("/{entry_id}/thumbnail")
  80. async def get_print_log_thumbnail(
  81. entry_id: int,
  82. db: AsyncSession = Depends(get_db),
  83. ):
  84. """Get the thumbnail for a print log entry.
  85. Note: Unauthenticated - loaded via <img> tags which can't send auth headers.
  86. """
  87. entry = await db.get(PrintLogEntry, entry_id)
  88. if not entry or not entry.thumbnail_path:
  89. raise HTTPException(404, "Thumbnail not found")
  90. thumb_path = settings.base_dir / entry.thumbnail_path
  91. if not thumb_path.exists():
  92. raise HTTPException(404, "Thumbnail file not found")
  93. return FileResponse(
  94. path=thumb_path,
  95. media_type="image/png",
  96. headers={"Cache-Control": "public, max-age=86400"},
  97. )
  98. @router.delete("/")
  99. async def clear_print_log(
  100. db: AsyncSession = Depends(get_db),
  101. _: User | None = RequirePermissionIfAuthEnabled(Permission.ARCHIVES_DELETE_ALL),
  102. ):
  103. """Clear the print log.
  104. Only deletes log entries. Archives and queue items are never touched.
  105. """
  106. result = await db.execute(delete(PrintLogEntry))
  107. deleted = result.rowcount
  108. await db.commit()
  109. logger.info("Print log cleared: %d entries deleted", deleted)
  110. return {"deleted": deleted}