
Updated Dewar API methods to use protected endpoints for enhanced security and consistency. Added `pgroups` handling in various frontend components and modified the LogisticsView contact field for clarity. Simplified backend router imports for better readability.
158 lines
5.1 KiB
Python
158 lines
5.1 KiB
Python
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
|
|
pgroup = sample.puck.dewar.pgroups
|
|
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/{pgroup}/{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,
|
|
}
|