diff --git a/backend/app/api/endpoints/device_types.py b/backend/app/api/endpoints/device_types.py new file mode 100644 index 00000000..7daa409c --- /dev/null +++ b/backend/app/api/endpoints/device_types.py @@ -0,0 +1,119 @@ +"""Device types API endpoints.""" +from typing import Annotated +from uuid import UUID + +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy import select, or_ +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.database import get_db +from app.api.deps import get_current_active_user +from app.models.user import User +from app.models.device_type import DeviceType +from app.schemas.device_type import ( + DeviceTypeCreate, + DeviceTypeUpdate, + DeviceTypeResponse, +) + +router = APIRouter(prefix="/device-types", tags=["device-types"]) + + +@router.get("/", response_model=list[DeviceTypeResponse]) +async def list_device_types( + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_active_user)], +) -> list[DeviceTypeResponse]: + stmt = ( + select(DeviceType) + .where( + or_( + DeviceType.is_system.is_(True), + DeviceType.team_id == current_user.team_id, + ) + ) + .order_by(DeviceType.category, DeviceType.sort_order, DeviceType.label) + ) + result = await db.execute(stmt) + rows = result.scalars().all() + return [DeviceTypeResponse.model_validate(r) for r in rows] + + +@router.post("/", response_model=DeviceTypeResponse, status_code=201) +async def create_device_type( + data: DeviceTypeCreate, + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_active_user)], +) -> DeviceTypeResponse: + existing = await db.execute( + select(DeviceType).where( + DeviceType.slug == data.slug, + DeviceType.team_id == current_user.team_id, + ) + ) + if existing.scalar_one_or_none(): + raise HTTPException(status_code=409, detail=f"Device type '{data.slug}' already exists for your team") + + system_existing = await db.execute( + select(DeviceType).where( + DeviceType.slug == data.slug, + DeviceType.is_system.is_(True), + ) + ) + if system_existing.scalar_one_or_none(): + raise HTTPException(status_code=409, detail=f"Device type '{data.slug}' conflicts with a system type") + + device_type = DeviceType( + slug=data.slug, + label=data.label, + category=data.category, + is_system=False, + team_id=current_user.team_id, + sort_order=data.sort_order, + ) + db.add(device_type) + await db.commit() + await db.refresh(device_type) + return DeviceTypeResponse.model_validate(device_type) + + +@router.put("/{device_type_id}", response_model=DeviceTypeResponse) +async def update_device_type( + device_type_id: UUID, + data: DeviceTypeUpdate, + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_active_user)], +) -> DeviceTypeResponse: + device_type = await db.get(DeviceType, device_type_id) + if not device_type: + raise HTTPException(status_code=404, detail="Device type not found") + if device_type.is_system: + raise HTTPException(status_code=403, detail="Cannot modify system device types") + if device_type.team_id != current_user.team_id: + raise HTTPException(status_code=404, detail="Device type not found") + + update_data = data.model_dump(exclude_unset=True) + for field, value in update_data.items(): + setattr(device_type, field, value) + + await db.commit() + await db.refresh(device_type) + return DeviceTypeResponse.model_validate(device_type) + + +@router.delete("/{device_type_id}", status_code=204) +async def delete_device_type( + device_type_id: UUID, + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_active_user)], +) -> None: + device_type = await db.get(DeviceType, device_type_id) + if not device_type: + raise HTTPException(status_code=404, detail="Device type not found") + if device_type.is_system: + raise HTTPException(status_code=403, detail="Cannot delete system device types") + if device_type.team_id != current_user.team_id: + raise HTTPException(status_code=404, detail="Device type not found") + + await db.delete(device_type) + await db.commit() diff --git a/backend/app/api/router.py b/backend/app/api/router.py index d588afc9..7c6367e0 100644 --- a/backend/app/api/router.py +++ b/backend/app/api/router.py @@ -33,6 +33,7 @@ from app.api.endpoints import beta_feedback from app.api.endpoints import session_branches from app.api.endpoints import session_handoffs from app.api.endpoints import session_resolutions +from app.api.endpoints import device_types api_router = APIRouter() @@ -92,3 +93,4 @@ api_router.include_router(script_builder.router) api_router.include_router(beta_feedback.router) api_router.include_router(session_branches.router) api_router.include_router(session_handoffs.router) +api_router.include_router(device_types.router)