from __future__ import annotations import uuid from typing import Any from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from api.database import get_session from api.models.repository_rename import RepositoryRenameOperation from api.schemas.repository_rename import ( ForgeIdentityVerifyRequest, RepositoryRenameOperationCreate, RepositoryRenameOperationRead, RepositoryRenamePhaseApply, RepositoryRenamePreflightRead, RepositoryRenamePreflightRequest, RepositoryRenameRollbackPreflightRead, RepositoryRenameRollbackRequest, RepositoryRenameVerificationRead, ) from api.services.forge_repository import ( ForgeRepositoryGateway, get_forge_repository_gateway, ) from api.services.repository_rename import ( RenameLifecycleError, apply_phase, apply_rollback, build_preflight, create_operation, list_operations, load_operation, load_operation_by_id, rollback_preflight, verify_forge_identity, verify_operation, ) router = APIRouter(prefix="/repos/{repo_id}", tags=["repository-renames"]) operation_router = APIRouter( prefix="/repository-renames", tags=["repository-renames"] ) def _raise(exc: RenameLifecycleError) -> None: raise HTTPException( status_code=exc.status_code, detail={"code": exc.code, "message": str(exc), "details": exc.details}, ) from exc def _operation_read(operation: RepositoryRenameOperation, *, no_op: bool = False) -> dict[str, Any]: return { "id": operation.id, "repo_id": operation.repo_id, "phase": operation.phase, "old_slug": operation.old_slug, "new_slug": operation.new_slug, "expected_forge_repository_id": operation.expected_forge_repository_id, "expected_source_commit": operation.expected_source_commit, "expected_default_branch": operation.expected_default_branch, "actor": operation.actor, "phase_changed_at": operation.phase_changed_at, "preflighted_at": operation.preflighted_at, "preflight_expires_at": operation.preflight_expires_at, "completed_at": operation.completed_at, "rolled_back_at": operation.rolled_back_at, "evidence": operation.evidence, "error_code": operation.error_code, "error_message": operation.error_message, "error_details": operation.error_details, "error_at": operation.error_at, "no_op": no_op, } @router.post("/forge-identity/verify") async def verify_repository_forge_identity( repo_id: uuid.UUID, body: ForgeIdentityVerifyRequest, session: AsyncSession = Depends(get_session), gateway: ForgeRepositoryGateway = Depends(get_forge_repository_gateway), ) -> dict[str, Any]: try: identity = await verify_forge_identity(session, gateway, repo_id, body) except RenameLifecycleError as exc: _raise(exc) return { "id": identity.id, "repo_id": identity.repo_id, "provider": identity.provider, "forge_instance": identity.forge_instance, "forge_owner": identity.forge_owner, "forge_repository_id": identity.forge_repository_id, "verification_state": identity.verification_state, "verified_at": identity.verified_at, "verified_by": identity.verified_by, "verification_evidence": identity.verification_evidence, } @router.post("/rename/preflight", response_model=RepositoryRenamePreflightRead) async def repository_rename_preflight( repo_id: uuid.UUID, body: RepositoryRenamePreflightRequest, session: AsyncSession = Depends(get_session), gateway: ForgeRepositoryGateway = Depends(get_forge_repository_gateway), ) -> dict[str, Any]: try: return await build_preflight(session, gateway, repo_id, body) except RenameLifecycleError as exc: _raise(exc) @router.post( "/rename/operations", response_model=RepositoryRenameOperationRead, status_code=status.HTTP_201_CREATED, ) async def create_repository_rename_operation( repo_id: uuid.UUID, body: RepositoryRenameOperationCreate, session: AsyncSession = Depends(get_session), gateway: ForgeRepositoryGateway = Depends(get_forge_repository_gateway), ) -> dict[str, Any]: try: operation, no_op = await create_operation(session, gateway, repo_id, body) except RenameLifecycleError as exc: _raise(exc) return _operation_read(operation, no_op=no_op) @operation_router.get( "/operations/{operation_id}", response_model=RepositoryRenameOperationRead ) async def get_repository_rename_operation_by_id( operation_id: uuid.UUID, session: AsyncSession = Depends(get_session), ) -> dict[str, Any]: """Resolve an operation journal without requiring its repository UUID.""" try: operation = await load_operation_by_id(session, operation_id) except RenameLifecycleError as exc: _raise(exc) return _operation_read(operation) @router.get( "/rename/operations", response_model=list[RepositoryRenameOperationRead] ) async def list_repository_rename_operations( repo_id: uuid.UUID, active_only: bool = False, session: AsyncSession = Depends(get_session), ) -> list[dict[str, Any]]: try: operations = await list_operations(session, repo_id, active_only=active_only) except RenameLifecycleError as exc: _raise(exc) return [_operation_read(operation) for operation in operations] @router.get( "/rename/operations/{operation_id}", response_model=RepositoryRenameOperationRead ) async def get_repository_rename_operation( repo_id: uuid.UUID, operation_id: uuid.UUID, session: AsyncSession = Depends(get_session), ) -> dict[str, Any]: try: operation = await load_operation(session, repo_id, operation_id) except RenameLifecycleError as exc: _raise(exc) return _operation_read(operation) @router.post( "/rename/operations/{operation_id}/phases/{phase}", response_model=RepositoryRenameOperationRead, ) async def apply_repository_rename_phase( repo_id: uuid.UUID, operation_id: uuid.UUID, phase: str, body: RepositoryRenamePhaseApply, session: AsyncSession = Depends(get_session), gateway: ForgeRepositoryGateway = Depends(get_forge_repository_gateway), ) -> dict[str, Any]: try: operation, no_op = await apply_phase( session, gateway, repo_id, operation_id, phase, body ) except RenameLifecycleError as exc: _raise(exc) return _operation_read(operation, no_op=no_op) @router.get( "/rename/operations/{operation_id}/verify", response_model=RepositoryRenameVerificationRead, ) async def verify_repository_rename_operation( repo_id: uuid.UUID, operation_id: uuid.UUID, session: AsyncSession = Depends(get_session), gateway: ForgeRepositoryGateway = Depends(get_forge_repository_gateway), ) -> dict[str, Any]: try: operation = await load_operation(session, repo_id, operation_id) return await verify_operation(session, gateway, operation) except RenameLifecycleError as exc: _raise(exc) @router.post( "/rename/operations/{operation_id}/rollback-preflight", response_model=RepositoryRenameRollbackPreflightRead, ) async def preflight_repository_rename_rollback( repo_id: uuid.UUID, operation_id: uuid.UUID, body: RepositoryRenameRollbackRequest, session: AsyncSession = Depends(get_session), gateway: ForgeRepositoryGateway = Depends(get_forge_repository_gateway), ) -> dict[str, Any]: try: operation, report = await rollback_preflight( session, gateway, repo_id, operation_id, expected_phase=body.expected_phase, confirmation=body.confirmation, ) except RenameLifecycleError as exc: _raise(exc) return { "operation_id": operation.id, "repo_id": operation.repo_id, **report, "operation": _operation_read(operation), } @router.post( "/rename/operations/{operation_id}/rollback", response_model=RepositoryRenameOperationRead, ) async def rollback_repository_rename_operation( repo_id: uuid.UUID, operation_id: uuid.UUID, body: RepositoryRenameRollbackRequest, session: AsyncSession = Depends(get_session), gateway: ForgeRepositoryGateway = Depends(get_forge_repository_gateway), ) -> dict[str, Any]: try: operation, no_op = await apply_rollback( session, gateway, repo_id, operation_id, expected_phase=body.expected_phase, confirmation=body.confirmation, ) except RenameLifecycleError as exc: _raise(exc) return _operation_read(operation, no_op=no_op)