Files
FarmaFinder/pip-platform/src/api/v1/endpoints/sync_jobs.py
T
Antoni Nuñez Romeu c5df0ec0b2
Run Tests on Branches / Backend Tests (push) Successful in 3m32s
Run Tests on Branches / Frontend Tests (push) Successful in 3m27s
First implementation API for PIP (Proyecto: Pharmacy Integration Platform)
2026-07-01 16:46:52 +02:00

200 lines
6.3 KiB
Python

from datetime import datetime
from typing import Any
from uuid import UUID
from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession
from src.api.v1.dependencies.auth import get_current_user, require_role
from src.core.exceptions import NotFoundException
from src.domain.entities import SyncJobStatus, SyncStrategy, UserRole
from src.infrastructure.database.repositories import SyncJobRepository, SyncLogRepository
from src.infrastructure.database.session import get_db_session
from src.services.auth.security import TokenData
from src.services.synchronization.sync_service import SyncService
router = APIRouter(prefix="/pharmacies/{pharmacy_id}/sync-jobs", tags=["Sync Jobs"])
class SyncJobCreateRequest(BaseModel):
connector_id: UUID
strategy: SyncStrategy = SyncStrategy.INCREMENTAL
class SyncJobResponse(BaseModel):
id: UUID
pharmacy_id: UUID
connector_id: UUID
strategy: str
status: str
started_at: datetime | None
completed_at: datetime | None
items_total: int | None
items_processed: int | None
items_failed: int | None
error_message: str | None
metadata: dict[str, Any] | None
created_at: datetime | None
updated_at: datetime | None
class SyncJobListResponse(BaseModel):
items: list[SyncJobResponse]
total: int
offset: int
limit: int
class SyncLogResponse(BaseModel):
id: UUID
sync_job_id: UUID
level: str
message: str
details: dict[str, Any] | None
created_at: datetime | None
def _entity_to_response(e: Any) -> SyncJobResponse:
return SyncJobResponse(
id=e.id, pharmacy_id=e.pharmacy_id, connector_id=e.connector_id,
strategy=e.strategy, status=e.status, started_at=e.started_at,
completed_at=e.completed_at, items_total=e.items_total,
items_processed=e.items_processed, items_failed=e.items_failed,
error_message=e.error_message, metadata=e.metadata_,
created_at=e.created_at, updated_at=e.updated_at,
)
def _get_service(session: AsyncSession) -> SyncService:
return SyncService(
sync_job_repo=SyncJobRepository(session),
sync_log_repo=SyncLogRepository(session),
)
@router.get("", response_model=SyncJobListResponse)
async def list_sync_jobs(
pharmacy_id: UUID,
status: str | None = None,
offset: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=200),
session: AsyncSession = Depends(get_db_session),
current_user: TokenData = Depends(get_current_user),
):
service = _get_service(session)
entities, total = await service.list_sync_jobs(pharmacy_id, status=status, offset=offset, limit=limit)
return SyncJobListResponse(items=[_entity_to_response(e) for e in entities], total=total, offset=offset, limit=limit)
@router.get("/{job_id}", response_model=SyncJobResponse)
async def get_sync_job(
pharmacy_id: UUID,
job_id: UUID,
session: AsyncSession = Depends(get_db_session),
current_user: TokenData = Depends(get_current_user),
):
service = _get_service(session)
entity = await service.get_sync_job(job_id)
if not entity or entity.pharmacy_id != pharmacy_id:
raise NotFoundException("SyncJob", str(job_id))
return _entity_to_response(entity)
@router.post("", response_model=SyncJobResponse, status_code=201)
async def create_sync_job(
pharmacy_id: UUID,
body: SyncJobCreateRequest,
session: AsyncSession = Depends(get_db_session),
current_user: TokenData = Depends(require_role(UserRole.OPERATOR)),
):
service = _get_service(session)
created = await service.create_sync_job(
pharmacy_id=pharmacy_id,
connector_id=body.connector_id,
strategy=body.strategy,
)
return _entity_to_response(created)
@router.post("/{job_id}/trigger", response_model=SyncJobResponse)
async def trigger_sync(
pharmacy_id: UUID,
job_id: UUID,
session: AsyncSession = Depends(get_db_session),
current_user: TokenData = Depends(require_role(UserRole.OPERATOR)),
):
service = _get_service(session)
updated = await service.trigger_sync(job_id)
return _entity_to_response(updated)
@router.post("/{job_id}/cancel", response_model=SyncJobResponse)
async def cancel_sync(
pharmacy_id: UUID,
job_id: UUID,
session: AsyncSession = Depends(get_db_session),
current_user: TokenData = Depends(require_role(UserRole.OPERATOR)),
):
service = _get_service(session)
updated = await service.cancel_sync(job_id)
return _entity_to_response(updated)
class SyncCompleteRequest(BaseModel):
items_total: int | None = None
items_processed: int | None = None
items_failed: int | None = None
duration_ms: int = 0
class SyncFailRequest(BaseModel):
error_message: str
@router.post("/{job_id}/complete", response_model=SyncJobResponse)
async def complete_sync(
pharmacy_id: UUID,
job_id: UUID,
body: SyncCompleteRequest,
session: AsyncSession = Depends(get_db_session),
current_user: TokenData = Depends(require_role(UserRole.OPERATOR)),
):
service = _get_service(session)
updated = await service.complete_sync(job_id, items_total=body.items_total, items_processed=body.items_processed, items_failed=body.items_failed)
return _entity_to_response(updated)
@router.post("/{job_id}/fail", response_model=SyncJobResponse)
async def fail_sync(
pharmacy_id: UUID,
job_id: UUID,
body: SyncFailRequest,
session: AsyncSession = Depends(get_db_session),
current_user: TokenData = Depends(require_role(UserRole.OPERATOR)),
):
service = _get_service(session)
updated = await service.fail_sync(job_id, error_message=body.error_message)
return _entity_to_response(updated)
@router.get("/{job_id}/logs", response_model=list[SyncLogResponse])
async def get_sync_logs(
pharmacy_id: UUID,
job_id: UUID,
level: str | None = None,
offset: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=500),
session: AsyncSession = Depends(get_db_session),
current_user: TokenData = Depends(get_current_user),
):
service = _get_service(session)
logs = await service.get_sync_logs(job_id, level=level, offset=offset, limit=limit)
return [
SyncLogResponse(
id=l.id, sync_job_id=l.sync_job_id, level=l.level,
message=l.message, details=l.details, created_at=l.created_at,
)
for l in logs
]