from fastapi import APIRouter, HTTPException, Depends, UploadFile, File from sqlalchemy.orm import Session from pathlib import Path from typing import List from datetime import datetime import shutil from app.schemas import ( Puck as PuckSchema, Sample as SampleSchema, SampleEventCreate, Sample, ) from app.models import ( Puck as PuckModel, Sample as SampleModel, SampleEvent as SampleEventModel, ) from app.dependencies import get_db import logging from sqlalchemy.orm import joinedload router = APIRouter() @router.get("/{puck_id}/samples", response_model=List[SampleSchema]) async def get_samples_with_events(puck_id: int, db: Session = Depends(get_db)): puck = db.query(PuckModel).filter(PuckModel.id == puck_id).first() if not puck: raise HTTPException(status_code=404, detail="Puck not found") samples = db.query(SampleModel).filter(SampleModel.puck_id == puck_id).all() for sample in samples: sample.events = ( db.query(SampleEventModel) .filter(SampleEventModel.sample_id == sample.id) .all() ) return samples @router.get("/pucks-samples", response_model=List[PuckSchema]) async def get_all_pucks_with_samples_and_events(db: Session = Depends(get_db)): logging.info("Fetching all pucks with samples and events") pucks = ( db.query(PuckModel) .options( joinedload(PuckModel.samples).joinedload( SampleModel.events ), # Correct nested relationship joinedload(PuckModel.events), # If Puck has its own events relationship ) .all() ) if not pucks: raise HTTPException(status_code=404, detail="No pucks found in the database") return pucks # Route to post a new sample event @router.post("/samples/{sample_id}/events", response_model=Sample) async def create_sample_event( sample_id: int, event: SampleEventCreate, db: Session = Depends(get_db) ): # Ensure the sample exists sample = db.query(SampleModel).filter(SampleModel.id == sample_id).first() if not sample: raise HTTPException(status_code=404, detail="Sample not found") # Create the event sample_event = SampleEventModel( sample_id=sample_id, event_type=event.event_type, timestamp=datetime.now(), # Use the current timestamp ) db.add(sample_event) db.commit() db.refresh(sample_event) # Load events for the sample to be serialized in the response sample.events = ( db.query(SampleEventModel).filter(SampleEventModel.sample_id == sample_id).all() ) return sample # Return the sample, now including `mount_count` @router.post("/samples/{sample_id}/upload-images") async def upload_sample_images( sample_id: int, uploaded_files: list[UploadFile] = File(...), db: Session = Depends(get_db), ): logging.info(f"Received files: {[file.filename for file in uploaded_files]}") """ Uploads images for a given sample and saves them to a directory structure. Args: sample_id (int): ID of the sample. uploaded_files (list[UploadFile]): A list of files uploaded with the request. db (Session): Database session. """ # 1. Validate Sample sample = db.query(SampleModel).filter(SampleModel.id == sample_id).first() if not sample: raise HTTPException(status_code=404, detail="Sample not found") # 2. Define Directory Structure username = "e16371" # Hardcoded username; replace with dynamic logic if applicable today = datetime.now().strftime("%Y-%m-%d") dewar_name = ( sample.puck.dewar.dewar_name if sample.puck and sample.puck.dewar else "default_dewar" ) puck_name = sample.puck.puck_name if sample.puck else "default_puck" position = sample.position if sample.position else "default_position" base_dir = Path(f"images/{username}/{today}/{dewar_name}/{puck_name}/{position}") base_dir.mkdir(parents=True, exist_ok=True) # 3. Process and Save Each File saved_files = [] for file in uploaded_files: # Validate MIME type if not file.content_type.startswith("image/"): raise HTTPException( status_code=400, detail=f"Invalid file type: {file.filename}. Only images are accepted.", ) # Save file to the base directory file_path = base_dir / file.filename # Save the file from the file stream try: with file_path.open("wb") as buffer: shutil.copyfileobj(file.file, buffer) saved_files.append(str(file_path)) # Track saved file paths except Exception as e: logging.error(f"Error saving file {file.filename}: {str(e)}") raise HTTPException( status_code=500, detail=f"Could not save file {file.filename}." f" Ensure the server has correct permissions.", ) # 4. Return Saved Files Information logging.info(f"Uploaded {len(saved_files)} files for sample {sample_id}.") return { "message": f"{len(saved_files)} images uploaded successfully.", "files": saved_files, }