from __future__ import annotations import imghdr import re from pathlib import Path from uuid import uuid4 from fastapi import HTTPException, UploadFile from sqlalchemy.orm import Session from app.modules.core.iam.models import User STATIC_ROOT = Path("app/ui/static") PROFILE_UPLOAD_DIR = STATIC_ROOT / "uploads" / "user_profiles" MAX_IMAGE_BYTES = 2 * 1024 * 1024 ALLOWED_IMAGE_TYPES = {"jpeg": ".jpg", "png": ".png", "gif": ".gif", "webp": ".webp"} def _blank_to_none(value: object) -> str | None: value = (str(value).strip() if value is not None else "") return value or None def _safe_static_path(path: str | None) -> str | None: path = (path or "").strip() if not path: return None if path.startswith("/static/"): return path if path.startswith("app/ui/static/"): return "/static/" + path.split("app/ui/static/", 1)[1].replace("\\", "/") return path def profile_photo_url(user: User | None) -> str | None: if not user: return None return _safe_static_path(getattr(user, "profile_photo_path", None)) def user_initials(user: User | None) -> str: if not user: return "U" name = (getattr(user, "full_name", None) or getattr(user, "email", "") or "User").strip() if "@" in name and not getattr(user, "full_name", None): name = name.split("@", 1)[0] parts = [p for p in re.split(r"\s+", name) if p] if not parts: return "U" if len(parts) == 1: return parts[0][:2].upper() return (parts[0][0] + parts[-1][0]).upper() async def save_user_profile_photo(user: User, upload: UploadFile | None) -> str | None: """Persist a profile photo and return a static path, or current path if nothing uploaded.""" if not upload or not getattr(upload, "filename", None): return getattr(user, "profile_photo_path", None) raw = await upload.read() if not raw: return getattr(user, "profile_photo_path", None) if len(raw) > MAX_IMAGE_BYTES: raise HTTPException(status_code=400, detail="Profile photo must be 2 MB or smaller.") detected = imghdr.what(None, raw) suffix = ALLOWED_IMAGE_TYPES.get(detected or "") if not suffix: raise HTTPException(status_code=400, detail="Upload a valid JPG, PNG, GIF or WebP profile photo.") PROFILE_UPLOAD_DIR.mkdir(parents=True, exist_ok=True) filename = f"user_{user.id}_{uuid4().hex}{suffix}" target = PROFILE_UPLOAD_DIR / filename target.write_bytes(raw) return f"app/ui/static/uploads/user_profiles/{filename}" def update_user_public_profile( db: Session, user: User, *, qualification: object = None, designation: object = None, mobile: object = None, bio: object = None, profile_photo_path: str | None = None, ) -> User: user.qualification = _blank_to_none(qualification) user.designation = _blank_to_none(designation) user.mobile = _blank_to_none(mobile) user.bio = _blank_to_none(bio) if profile_photo_path is not None: user.profile_photo_path = profile_photo_path db.add(user) return user