"""Resolve report section photo paths under ``settings.upload_dir``.""" from __future__ import annotations from pathlib import Path from app.config import settings _PHOTO_EXTENSIONS = ("jpg", "jpeg", "png", "webp") def report_photo_relpath( tenant_id: str, report_id: str, section_code: str, photo_id: str, ext: str, ) -> str: """Relative path stored in ``ReportSectionPhoto.file_path`` (POSIX-style).""" ext_norm = ext.lower().lstrip(".") if ext_norm == "jpeg": ext_norm = "jpg" return "/".join( ( tenant_id, "report_photos", str(report_id), str(section_code), f"{photo_id}.{ext_norm}", ) ) def resolve_report_photo_path( file_path: str, *, tenant_id: str | None = None, report_id: str | None = None, section_code: str | None = None, photo_id: str | None = None, ) -> str | None: """Return an on-disk path for python-docx / FileResponse, or ``None``.""" raw = (file_path or "").strip() if not raw: return None upload_root = settings.upload_dir.resolve() candidate = Path(raw) if candidate.is_file(): return str(candidate.resolve()) # Path stored relative to upload_dir (preferred). rel = Path(raw.replace("\\", "/")) if not rel.is_absolute(): under = (upload_root / rel).resolve() if under.is_file(): return str(under) # Absolute path from another host/container — re-root under upload_dir when possible. parts = tuple(Path(raw).parts) if "report_photos" in parts: idx = parts.index("report_photos") if idx >= 1: suffix = Path(*parts[idx - 1 :]) under = (upload_root / suffix).resolve() if under.is_file(): return str(under) if tenant_id and report_id and section_code and photo_id: section_dir = upload_root / tenant_id / "report_photos" / str(report_id) / str(section_code) if section_dir.is_dir(): for ext in _PHOTO_EXTENSIONS: cand = section_dir / f"{photo_id}.{ext}" if cand.is_file(): return str(cand.resolve()) name = Path(raw).name if name: cand = section_dir / name if cand.is_file(): return str(cand.resolve()) return None