7bc27da33a
Upload documents for AI context, exam images for Moodle questions, per-template storage limits, embedded images in XML export, and GUIA_API_Y_FLUJO.md with full endpoint documentation.
82 lines
2.8 KiB
Python
82 lines
2.8 KiB
Python
import uuid
|
|
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.config import Settings
|
|
from app.core.errors import AppError
|
|
from app.models.exam import ExamImage, ExamMaterial
|
|
|
|
|
|
class StorageQuotaService:
|
|
def __init__(self, db: Session, settings: Settings) -> None:
|
|
self.db = db
|
|
self.settings = settings
|
|
|
|
def get_template_usage_bytes(self, template_id: uuid.UUID) -> int:
|
|
materials_bytes = self.db.scalar(
|
|
select(func.coalesce(func.sum(ExamMaterial.size_bytes), 0)).where(
|
|
ExamMaterial.template_id == template_id
|
|
)
|
|
)
|
|
images_bytes = self.db.scalar(
|
|
select(func.coalesce(func.sum(ExamImage.size_bytes), 0)).where(
|
|
ExamImage.template_id == template_id
|
|
)
|
|
)
|
|
return int(materials_bytes or 0) + int(images_bytes or 0)
|
|
|
|
def ensure_template_has_space(self, template_id: uuid.UUID, incoming_bytes: int) -> None:
|
|
if incoming_bytes <= 0:
|
|
return
|
|
|
|
limit = self.settings.max_storage_bytes_per_template
|
|
used = self.get_template_usage_bytes(template_id)
|
|
projected = used + incoming_bytes
|
|
|
|
if projected > limit:
|
|
raise AppError(
|
|
message=(
|
|
f"Template storage quota exceeded. "
|
|
f"Limit: {self._format_mb(limit)}, "
|
|
f"used: {self._format_mb(used)}, "
|
|
f"file: {self._format_mb(incoming_bytes)}"
|
|
),
|
|
status_code=413,
|
|
code="template_storage_quota_exceeded",
|
|
)
|
|
|
|
def get_usage_summary(self, template_id: uuid.UUID) -> dict[str, int | float]:
|
|
materials_bytes = int(
|
|
self.db.scalar(
|
|
select(func.coalesce(func.sum(ExamMaterial.size_bytes), 0)).where(
|
|
ExamMaterial.template_id == template_id
|
|
)
|
|
)
|
|
or 0
|
|
)
|
|
images_bytes = int(
|
|
self.db.scalar(
|
|
select(func.coalesce(func.sum(ExamImage.size_bytes), 0)).where(
|
|
ExamImage.template_id == template_id
|
|
)
|
|
)
|
|
or 0
|
|
)
|
|
used = materials_bytes + images_bytes
|
|
limit = self.settings.max_storage_bytes_per_template
|
|
return {
|
|
"template_id": template_id,
|
|
"used_bytes": used,
|
|
"limit_bytes": limit,
|
|
"remaining_bytes": max(limit - used, 0),
|
|
"materials_bytes": materials_bytes,
|
|
"images_bytes": images_bytes,
|
|
"used_mb": round(used / (1024 * 1024), 2),
|
|
"limit_mb": round(limit / (1024 * 1024), 2),
|
|
}
|
|
|
|
@staticmethod
|
|
def _format_mb(value_bytes: int) -> str:
|
|
return f"{value_bytes / (1024 * 1024):.2f} MB"
|