feat: add repository rename lifecycle API
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a049a4-ee9f-78e1-9d66-2cb0f9bea3e3
This commit is contained in:
parent
9e77a4a9a2
commit
82ea38b180
11 changed files with 2464 additions and 3 deletions
251
api/routers/repository_renames.py
Normal file
251
api/routers/repository_renames.py
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
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,
|
||||
rollback_preflight,
|
||||
verify_forge_identity,
|
||||
verify_operation,
|
||||
)
|
||||
|
||||
|
||||
router = APIRouter(prefix="/repos/{repo_id}", 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 = await create_operation(session, gateway, repo_id, body)
|
||||
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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue