mitai-jinkendo/backend/routers/photos.py
Lars b4a1856f79
All checks were successful
Deploy Development / deploy (push) Successful in 58s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 13s
refactor: modular backend architecture with 14 router modules
Phase 2 Complete - Backend Refactoring:
- Extracted all endpoints to dedicated router modules
- main.py: 1878 → 75 lines (-96% reduction)
- Created modular structure for maintainability

Router Structure (60 endpoints total):
├── auth.py          - 7 endpoints (login, logout, password reset)
├── profiles.py      - 7 endpoints (CRUD + current user)
├── weight.py        - 5 endpoints (tracking + stats)
├── circumference.py - 4 endpoints (body measurements)
├── caliper.py       - 4 endpoints (skinfold tracking)
├── activity.py      - 6 endpoints (workouts + Apple Health import)
├── nutrition.py     - 4 endpoints (diet + FDDB import)
├── photos.py        - 3 endpoints (progress photos)
├── insights.py      - 8 endpoints (AI analysis + pipeline)
├── prompts.py       - 2 endpoints (AI prompt management)
├── admin.py         - 7 endpoints (user management)
├── stats.py         - 1 endpoint (dashboard stats)
├── exportdata.py    - 3 endpoints (CSV/JSON/ZIP export)
└── importdata.py    - 1 endpoint (ZIP import)

Core modules maintained:
- db.py: PostgreSQL connection + helpers
- auth.py: Auth functions (hash, verify, sessions)
- models.py: 11 Pydantic models

Benefits:
- Self-contained modules with clear responsibilities
- Easier to navigate and modify specific features
- Improved code organization and readability
- 100% functional compatibility maintained
- All syntax checks passed

Updated CLAUDE.md with new architecture documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 11:15:35 +01:00

64 lines
2.3 KiB
Python

"""
Photo Management Endpoints for Mitai Jinkendo
Handles progress photo uploads and retrieval.
"""
import os
import uuid
from pathlib import Path
from typing import Optional
from fastapi import APIRouter, UploadFile, File, Header, HTTPException, Depends
from fastapi.responses import FileResponse
import aiofiles
from db import get_db, get_cursor, r2d
from auth import require_auth, require_auth_flexible
from routers.profiles import get_pid
router = APIRouter(prefix="/api/photos", tags=["photos"])
PHOTOS_DIR = Path(os.getenv("PHOTOS_DIR", "./photos"))
PHOTOS_DIR.mkdir(parents=True, exist_ok=True)
@router.post("")
async def upload_photo(file: UploadFile=File(...), date: str="",
x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
"""Upload progress photo."""
pid = get_pid(x_profile_id)
fid = str(uuid.uuid4())
ext = Path(file.filename).suffix or '.jpg'
path = PHOTOS_DIR / f"{fid}{ext}"
async with aiofiles.open(path,'wb') as f: await f.write(await file.read())
with get_db() as conn:
cur = get_cursor(conn)
cur.execute("INSERT INTO photos (id,profile_id,date,path,created) VALUES (%s,%s,%s,%s,CURRENT_TIMESTAMP)",
(fid,pid,date,str(path)))
return {"id":fid,"date":date}
@router.get("/{fid}")
def get_photo(fid: str, session: dict=Depends(require_auth_flexible)):
"""Get photo by ID. Auth via header or query param (for <img> tags)."""
with get_db() as conn:
cur = get_cursor(conn)
cur.execute("SELECT path FROM photos WHERE id=%s", (fid,))
row = cur.fetchone()
if not row: raise HTTPException(404, "Photo not found")
photo_path = Path(PHOTOS_DIR) / row['path']
if not photo_path.exists():
raise HTTPException(404, "Photo file not found")
return FileResponse(photo_path)
@router.get("")
def list_photos(x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)):
"""Get all photos for current profile."""
pid = get_pid(x_profile_id)
with get_db() as conn:
cur = get_cursor(conn)
cur.execute(
"SELECT * FROM photos WHERE profile_id=%s ORDER BY created DESC LIMIT 100", (pid,))
return [r2d(r) for r in cur.fetchall()]