200 lines
6.3 KiB
Python
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
|
|
]
|