from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select, update as sa_update from sqlalchemy.orm import selectinload from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_current_user, get_db from app.models.object import Object from app.models.rack import Rack from app.models.user import User from app.schemas.rack import RackCreate, RackRead, RackUpdate router = APIRouter(prefix="/api/v1/objects/{obj_id}/racks", tags=["racks"]) async def _touch_object(db: AsyncSession, obj_id: int) -> None: await db.execute( sa_update(Object).where(Object.id == obj_id).values(updated_at=datetime.now(timezone.utc)) ) async def _get_object(db: AsyncSession, obj_id: int) -> Object: obj = await db.get(Object, obj_id) if obj is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Object not found") return obj async def _load_rack(db: AsyncSession, obj_id: int, rack_id: int) -> Rack: result = await db.execute( select(Rack) .options(selectinload(Rack.zone)) .where(Rack.id == rack_id, Rack.object_id == obj_id) ) rack = result.scalar_one_or_none() if rack is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Rack not found") return rack @router.get("", response_model=list[RackRead]) async def list_racks( obj_id: int, db: AsyncSession = Depends(get_db), _: User = Depends(get_current_user), ): await _get_object(db, obj_id) result = await db.execute( select(Rack) .options(selectinload(Rack.zone)) .where(Rack.object_id == obj_id) .order_by(Rack.name) ) return [RackRead.from_orm_with_zone(r) for r in result.scalars().all()] @router.post("", response_model=RackRead, status_code=status.HTTP_201_CREATED) async def create_rack( obj_id: int, body: RackCreate, db: AsyncSession = Depends(get_db), _: User = Depends(get_current_user), ): await _get_object(db, obj_id) rack = Rack(object_id=obj_id, **body.model_dump()) db.add(rack) await db.flush() await _touch_object(db, obj_id) result = await db.execute( select(Rack).options(selectinload(Rack.zone)).where(Rack.id == rack.id) ) return RackRead.from_orm_with_zone(result.scalar_one()) @router.patch("/{rack_id}", response_model=RackRead) async def update_rack( obj_id: int, rack_id: int, body: RackUpdate, db: AsyncSession = Depends(get_db), _: User = Depends(get_current_user), ): rack = await _load_rack(db, obj_id, rack_id) for field, value in body.model_dump(exclude_unset=True).items(): setattr(rack, field, value) await db.flush() await _touch_object(db, obj_id) result = await db.execute( select(Rack).options(selectinload(Rack.zone)).where(Rack.id == rack.id) ) return RackRead.from_orm_with_zone(result.scalar_one()) @router.delete("/{rack_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_rack( obj_id: int, rack_id: int, db: AsyncSession = Depends(get_db), _: User = Depends(get_current_user), ): rack = await _load_rack(db, obj_id, rack_id) await db.delete(rack) await _touch_object(db, obj_id)