Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a049a4-ee9f-78e1-9d66-2cb0f9bea3e3
270 lines
8.7 KiB
Python
270 lines
8.7 KiB
Python
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)
|