"""HTTP mapping for explicit repair workflows.""" from typing import Annotated from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, status from app.identity import require_permission from app.repairs.dependencies import get_repair_service from app.repairs.exceptions import ( ControlBoardReplacementActorRequired, InvalidMacAddress, RepairActorRequired, RepairCompletionActorRequired, RepairConflict, RepairMinerNotFound, RepairNotFound, RepairPartActorRequired, RepairPartComponentNotFound, RepairPartNotFound, RepairPartValidationError, ) from app.repairs.models import ( BeginRepairRequest, ControlBoardReplacementRequest, ControlBoardReplacementResponse, BeginRepairResponse, RepairCompleteRequest, RepairDetailResponse, RepairTimelineResponse, RepairPartListResponse, RepairPartRecordRequest, RepairPartResponse, RepairPartVoidRequest, ) from app.repairs.service import RepairService router = APIRouter(prefix="/api/repairs", tags=["repairs"]) control_board_router = APIRouter(prefix="/api/miners", tags=["miners"]) require_repair_create = require_permission("repairs.create") require_repair_read = require_permission("repairs.read") require_repair_complete = require_permission("repairs.complete") require_repair_parts_manage = require_permission("repairs.parts.manage") require_control_board_replace = require_permission("miners.replace_control_board") RepairServiceDependency = Annotated[ RepairService, Depends(get_repair_service), ] RepairCreateUser = Annotated[dict, Depends(require_repair_create)] RepairReadUser = Annotated[dict, Depends(require_repair_read)] RepairCompleteUser = Annotated[dict, Depends(require_repair_complete)] RepairPartsManageUser = Annotated[dict, Depends(require_repair_parts_manage)] ControlBoardReplaceUser = Annotated[dict, Depends(require_control_board_replace)] @control_board_router.post( "/{miner_id}/replace-control-board", response_model=ControlBoardReplacementResponse, ) def replace_control_board( miner_id: UUID, request: ControlBoardReplacementRequest, service: RepairServiceDependency, current_user: ControlBoardReplaceUser, ) -> ControlBoardReplacementResponse: try: result = service.replace_control_board( miner_id, request, actor_user_id=UUID(str(current_user["id"])), ) except RepairMinerNotFound as exc: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Miner not found" ) from exc except RepairConflict as exc: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=str(exc) ) from exc except (InvalidMacAddress, ControlBoardReplacementActorRequired) as exc: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc) ) from exc return ControlBoardReplacementResponse(data=result) @router.post( "", response_model=BeginRepairResponse, status_code=status.HTTP_201_CREATED, ) def begin_repair( request: BeginRepairRequest, service: RepairServiceDependency, current_user: RepairCreateUser, ) -> BeginRepairResponse: try: repair = service.begin_repair( request, actor_user_id=UUID(str(current_user["id"])), ) except RepairMinerNotFound as exc: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Miner not found", ) from exc except RepairConflict as exc: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=str(exc), ) from exc except RepairActorRequired as exc: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc), ) from exc return BeginRepairResponse(data=repair) @router.post( "/{repair_id}/complete", response_model=RepairDetailResponse, ) def complete_repair( repair_id: UUID, request: RepairCompleteRequest, service: RepairServiceDependency, current_user: RepairCompleteUser, ) -> RepairDetailResponse: try: repair = service.complete_repair( repair_id, request, actor_user_id=UUID(str(current_user["id"])), ) except RepairNotFound as exc: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Repair not found", ) from exc except RepairMinerNotFound as exc: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Repair miner no longer exists", ) from exc except RepairConflict as exc: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=str(exc), ) from exc except RepairCompletionActorRequired as exc: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc), ) from exc return RepairDetailResponse(data=repair) @router.get( "/{repair_id}/parts", response_model=RepairPartListResponse, ) def list_repair_parts( repair_id: UUID, service: RepairServiceDependency, current_user: RepairReadUser, ) -> RepairPartListResponse: del current_user try: parts = service.list_repair_parts(repair_id) except RepairNotFound as exc: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Repair not found", ) from exc return RepairPartListResponse(data=parts) @router.post( "/{repair_id}/parts", response_model=RepairPartResponse, status_code=status.HTTP_201_CREATED, ) def record_repair_part( repair_id: UUID, request: RepairPartRecordRequest, service: RepairServiceDependency, current_user: RepairPartsManageUser, ) -> RepairPartResponse: try: part = service.record_repair_part( repair_id, request, actor_user_id=UUID(str(current_user["id"])), ) except (RepairNotFound, RepairPartComponentNotFound) as exc: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=str(exc), ) from exc except RepairMinerNotFound as exc: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Repair miner no longer exists", ) from exc except RepairConflict as exc: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=str(exc), ) from exc except (RepairPartActorRequired, RepairPartValidationError) as exc: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc), ) from exc return RepairPartResponse(data=part) @router.post( "/{repair_id}/parts/{repair_part_id}/void", response_model=RepairPartResponse, ) def void_repair_part( repair_id: UUID, repair_part_id: UUID, request: RepairPartVoidRequest, service: RepairServiceDependency, current_user: RepairPartsManageUser, ) -> RepairPartResponse: try: part = service.void_repair_part( repair_id, repair_part_id, request, actor_user_id=UUID(str(current_user["id"])), ) except (RepairNotFound, RepairPartNotFound) as exc: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=str(exc), ) from exc except RepairMinerNotFound as exc: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Repair miner no longer exists", ) from exc except RepairConflict as exc: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=str(exc), ) from exc except (RepairPartActorRequired, RepairPartValidationError) as exc: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc), ) from exc return RepairPartResponse(data=part) @router.get( "/{repair_id}", response_model=RepairDetailResponse, ) def get_repair( repair_id: UUID, service: RepairServiceDependency, current_user: RepairReadUser, ) -> RepairDetailResponse: del current_user try: repair = service.get_repair(repair_id) except RepairNotFound as exc: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Repair not found", ) from exc return RepairDetailResponse(data=repair) @router.get( "/{repair_id}/events", response_model=RepairTimelineResponse, ) def get_repair_events( repair_id: UUID, service: RepairServiceDependency, current_user: RepairReadUser, ) -> RepairTimelineResponse: del current_user try: timeline = service.get_repair_timeline(repair_id) except RepairNotFound as exc: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Repair not found", ) from exc return RepairTimelineResponse(data=timeline)