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, SampleEventResponse, SampleEventCreate, ) from app.models import ( Puck as PuckModel, Sample as SampleModel, SampleEvent as SampleEventModel, ) from app.dependencies import get_db import logging 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).all() logging.info(f"Found {len(pucks)} pucks in the database") for puck in pucks: if puck.dewar_id is None: puck.dewar_id = -1 logging.info(f"Puck ID: {puck.id}, Name: {puck.puck_name}") if not pucks: raise HTTPException( status_code=404, detail="No pucks found in the database" ) # More descriptive return pucks # Route to post a new sample event @router.post("/samples/{sample_id}/events", response_model=SampleEventResponse) 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) return ( sample_event # Response will automatically use the SampleEventResponse schema ) # Route to fetch the last (most recent) sample event @router.get("/samples/{sample_id}/events/last", response_model=SampleEventResponse) async def get_last_sample_event(sample_id: int, 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") # Get the most recent event for the sample last_event = ( db.query(SampleEventModel) .filter(SampleEventModel.sample_id == sample_id) .order_by(SampleEventModel.timestamp.desc()) .first() ) if not last_event: raise HTTPException(status_code=404, detail="No events found for the sample") return SampleEventResponse( id=last_event.id, sample_id=last_event.sample_id, event_type=last_event.event_type, timestamp=last_event.timestamp, ) # Response will automatically use the SampleEventResponse schema @router.post("/samples/{sample_id}/upload-images") async def upload_sample_images( sample_id: int, uploaded_files: List[UploadFile] = File(...), # Accept multiple files db: Session = Depends(get_db), ): """ Uploads images for a sample and stores them in a directory structure: images/user/date/dewar_name/puck_name/position/. Args: sample_id (int): ID of the sample. uploaded_files (List[UploadFile]): List of image files to be uploaded. db (Session): SQLAlchemy database session. """ # Fetch sample details from the database sample = db.query(SampleModel).filter(SampleModel.id == sample_id).first() if not sample: raise HTTPException(status_code=404, detail="Sample not found") # Retrieve associated dewar_name, puck_name and position puck = sample.puck if not puck: raise HTTPException( status_code=404, detail=f"No puck associated with sample ID {sample_id}" ) dewar_name = puck.dewar.dewar_name if puck.dewar else None if not dewar_name: raise HTTPException( status_code=404, detail=f"No dewar associated with puck ID {puck.id}" ) puck_name = puck.puck_name position = sample.position # Retrieve username (hardcoded for now—can be fetched dynamically if needed) username = "e16371" # Today's date in the format YYYY-MM-DD today = datetime.now().strftime("%Y-%m-%d") # Generate the directory path based on the structure base_dir = ( Path("images") / username / today / dewar_name / puck_name / str(position) ) # Create directories if they don't exist base_dir.mkdir(parents=True, exist_ok=True) # Save each uploaded image to the directory for file in uploaded_files: # Validate file content type if not file.content_type.startswith("image/"): raise HTTPException( status_code=400, detail=f"Invalid file type: {file.filename}. Must be an image.", ) # Create a file path for storing the uploaded file file_path = base_dir / file.filename try: # Save the file with file_path.open("wb") as buffer: shutil.copyfileobj(file.file, buffer) except Exception as e: raise HTTPException( status_code=500, detail=f"Error saving file {file.filename}: {str(e)}", ) return { "message": f"{len(uploaded_files)} images uploaded successfully.", "path": str(base_dir), # Return the base directory for reference }