from datetime import datetime from typing import TypeVar from uuid import uuid4 from core_domain import ServiceHealth from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import text from sqlalchemy.orm import Session from app.application.services import SkillApplicationService from app.db.session import get_db from app.domain.repositories import ( SkillDefinitionRepository, SkillInstallationRepository, SkillRunRepository, ) from app.schemas.skill import ( ApiResponse, DeleteData, PageResult, SkillCreateRequestDto, SkillDefinitionDto, SkillDeleteRequestDto, SkillDetailRequestDto, SkillInstallRequestDto, SkillInstallationDto, SkillInstallationListRequestDto, SkillInstallationStatusRequestDto, SkillListRequestDto, SkillRunCreateRequestDto, SkillRunDto, SkillRunExecuteRequestDto, SkillStatusRequestDto, SkillUpdateRequestDto, ) router = APIRouter() T = TypeVar("T") def ok(data: T) -> ApiResponse[T]: return ApiResponse( success=True, data=data, error=None, requestId=str(uuid4()), serverTime=datetime.utcnow()) def get_skill_application_service(db: Session = Depends(get_db)) -> SkillApplicationService: return SkillApplicationService( skill_repository=SkillDefinitionRepository(db), installation_repository=SkillInstallationRepository(db), skill_run_repository=SkillRunRepository(db)) @router.get("/health", response_model=ServiceHealth) def health_check(db: Session = Depends(get_db)) -> ServiceHealth: db.execute(text("SELECT 1")) return ServiceHealth(service="skill-service", status="ok", database="ok") @router.post("/list", response_model=ApiResponse[PageResult[SkillDefinitionDto]]) def list_skills_contract( payload: SkillListRequestDto, service: SkillApplicationService = Depends(get_skill_application_service)) -> ApiResponse[PageResult[SkillDefinitionDto]]: items, total = service.list_skills_contract(payload) return ok(PageResult[SkillDefinitionDto].from_items( items=[SkillDefinitionDto.from_entity(item) for item in items], total=total, page=payload.page, page_size=payload.pageSize)) @router.post("/create", response_model=ApiResponse[SkillDefinitionDto]) def create_skill_contract( payload: SkillCreateRequestDto, service: SkillApplicationService = Depends(get_skill_application_service)) -> ApiResponse[SkillDefinitionDto]: return ok(SkillDefinitionDto.from_entity(service.create_skill_from_contract(payload))) @router.post("/detail", response_model=ApiResponse[SkillDefinitionDto]) def detail_skill_contract( payload: SkillDetailRequestDto, service: SkillApplicationService = Depends(get_skill_application_service)) -> ApiResponse[SkillDefinitionDto]: entity = service.skill_repository.get_by_id(skill_id=payload.skillId) if entity is None: raise HTTPException(status_code=404, detail=f"skill not found: {payload.skillId}") return ok(SkillDefinitionDto.from_entity(entity)) @router.post("/update", response_model=ApiResponse[SkillDefinitionDto]) def update_skill_contract( payload: SkillUpdateRequestDto, service: SkillApplicationService = Depends(get_skill_application_service)) -> ApiResponse[SkillDefinitionDto]: entity = service.update_skill(payload) if entity is None: raise HTTPException(status_code=404, detail=f"skill not found: {payload.skillId}") return ok(SkillDefinitionDto.from_entity(entity)) @router.post("/status", response_model=ApiResponse[SkillDefinitionDto]) def update_skill_status_contract( payload: SkillStatusRequestDto, service: SkillApplicationService = Depends(get_skill_application_service)) -> ApiResponse[SkillDefinitionDto]: entity = service.update_skill_status_contract(payload) if entity is None: raise HTTPException(status_code=404, detail=f"skill not found: {payload.skillId}") return ok(SkillDefinitionDto.from_entity(entity)) @router.post("/delete", response_model=ApiResponse[DeleteData]) def delete_skill_contract( payload: SkillDeleteRequestDto, service: SkillApplicationService = Depends(get_skill_application_service)) -> ApiResponse[DeleteData]: entity = service.delete_skill(payload) if entity is None: raise HTTPException(status_code=404, detail=f"skill not found: {payload.skillId}") return ok(DeleteData(deleted=True, skillId=payload.skillId)) @router.post("/installations/list", response_model=ApiResponse[PageResult[SkillInstallationDto]]) def list_installations_contract( payload: SkillInstallationListRequestDto, service: SkillApplicationService = Depends(get_skill_application_service)) -> ApiResponse[PageResult[SkillInstallationDto]]: items, total = service.list_installations_contract(payload) return ok(PageResult[SkillInstallationDto].from_items( items=[SkillInstallationDto.from_entity(item) for item in items], total=total, page=payload.page, page_size=payload.pageSize)) @router.post("/installations/create", response_model=ApiResponse[SkillInstallationDto]) def install_skill_contract( payload: SkillInstallRequestDto, service: SkillApplicationService = Depends(get_skill_application_service)) -> ApiResponse[SkillInstallationDto]: try: return ok(SkillInstallationDto.from_entity(service.install_skill_from_contract(payload))) except ValueError as exc: raise HTTPException(status_code=422, detail=str(exc)) from exc @router.post("/installations/status", response_model=ApiResponse[SkillInstallationDto]) def update_installation_status_contract( payload: SkillInstallationStatusRequestDto, service: SkillApplicationService = Depends(get_skill_application_service)) -> ApiResponse[SkillInstallationDto]: entity = service.update_installation_status_contract(payload) if entity is None: raise HTTPException(status_code=404, detail=f"skill installation not found: {payload.installationId}") return ok(SkillInstallationDto.from_entity(entity)) @router.post("/runs/create", response_model=ApiResponse[SkillRunDto]) def create_skill_run( payload: SkillRunCreateRequestDto, service: SkillApplicationService = Depends(get_skill_application_service)) -> ApiResponse[SkillRunDto]: try: return ok(SkillRunDto.from_entity(service.create_skill_run_from_contract(payload))) except ValueError as exc: raise HTTPException(status_code=422, detail=str(exc)) from exc @router.post("/runs/execute", response_model=ApiResponse[SkillRunDto]) def execute_skill_run( payload: SkillRunExecuteRequestDto, service: SkillApplicationService = Depends(get_skill_application_service)) -> ApiResponse[SkillRunDto]: entity = service.execute_skill_run_from_contract(payload) if entity is None: raise HTTPException(status_code=404, detail=f"skill_run not found: {payload.skillRunId}") return ok(SkillRunDto.from_entity(entity))