mirror of
https://github.com/velocitatem/cvfs.git
synced 2026-05-31 08:43:37 +00:00
feat: allow updating existing CV branches
This commit is contained in:
@@ -4,8 +4,12 @@ from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import get_current_user, get_db
|
||||
from app.schemas import BranchCreateRequest, VersionResponse
|
||||
from app.services.versions import create_branch, delete_version
|
||||
from app.schemas import BranchCreateRequest, PatchApplyRequest, VersionResponse
|
||||
from app.services.versions import (
|
||||
append_patches_to_version,
|
||||
create_branch,
|
||||
delete_version,
|
||||
)
|
||||
from dlib.auth import AuthenticatedUser
|
||||
from dlib.cv.ats_guard import PatchValidationError
|
||||
|
||||
@@ -48,3 +52,26 @@ async def delete_version_branch(
|
||||
raise HTTPException(status_code=400, detail="Cannot delete root version")
|
||||
if result == "has_children":
|
||||
raise HTTPException(status_code=409, detail="Delete child branches first")
|
||||
|
||||
|
||||
@router.post("/{version_id}/patches", response_model=VersionResponse)
|
||||
async def append_patches(
|
||||
version_id: str,
|
||||
payload: PatchApplyRequest,
|
||||
session: AsyncSession = Depends(get_db),
|
||||
user: AuthenticatedUser = Depends(get_current_user),
|
||||
):
|
||||
if not payload.patches:
|
||||
raise HTTPException(status_code=400, detail="No patches provided")
|
||||
try:
|
||||
version = await append_patches_to_version(
|
||||
session,
|
||||
owner_id=user.sub,
|
||||
version_id=version_id,
|
||||
patches=payload.patches,
|
||||
)
|
||||
except PatchValidationError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
if not version:
|
||||
raise HTTPException(status_code=404, detail="Version not found")
|
||||
return VersionResponse.model_validate(version)
|
||||
|
||||
@@ -4,6 +4,7 @@ from .cv import (
|
||||
DocumentCreateResult,
|
||||
DocumentListResponse,
|
||||
DocumentResponse,
|
||||
PatchApplyRequest,
|
||||
PublicAssetAnalyticsResponse,
|
||||
PublicAssetLookupResponse,
|
||||
PublicAssetResponse,
|
||||
@@ -21,6 +22,7 @@ __all__ = [
|
||||
"DocumentCreateResult",
|
||||
"VersionResponse",
|
||||
"BranchCreateRequest",
|
||||
"PatchApplyRequest",
|
||||
"SubmissionCreateRequest",
|
||||
"SubmissionResponse",
|
||||
"AiSuggestionRequest",
|
||||
|
||||
@@ -63,6 +63,10 @@ class BranchCreateRequest(BaseModel):
|
||||
patches: list[dict[str, Any]] = Field(default_factory=list)
|
||||
|
||||
|
||||
class PatchApplyRequest(BaseModel):
|
||||
patches: list[dict[str, Any]] = Field(default_factory=list)
|
||||
|
||||
|
||||
class SubmissionResponse(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
@@ -84,6 +84,66 @@ async def create_branch(
|
||||
return result.scalars().one()
|
||||
|
||||
|
||||
async def append_patches_to_version(
|
||||
session: AsyncSession,
|
||||
*,
|
||||
owner_id: str,
|
||||
version_id: str,
|
||||
patches: list[dict],
|
||||
) -> CvVersion | None:
|
||||
stmt = (
|
||||
select(CvVersion)
|
||||
.join(CvVersion.document)
|
||||
.where(CvVersion.id == version_id, CvDocument.owner_id == owner_id)
|
||||
.options(selectinload(CvVersion.patches))
|
||||
)
|
||||
result = await session.execute(stmt)
|
||||
version = result.scalars().one_or_none()
|
||||
if not version:
|
||||
return None
|
||||
|
||||
patch_models = [PatchPayload.model_validate(item) for item in patches]
|
||||
if not patch_models:
|
||||
return version
|
||||
|
||||
base_doc = StructuredDocument(
|
||||
version_label=version.version_label,
|
||||
blocks=[
|
||||
StructuredBlock.model_validate(block)
|
||||
for block in version.structured_blocks or []
|
||||
],
|
||||
)
|
||||
validate_patchset(base_doc, patch_models)
|
||||
updated_doc = apply_patchset(base_doc, patch_models)
|
||||
|
||||
version.structured_blocks = [block.model_dump() for block in updated_doc.blocks]
|
||||
metadata = version.metadata_json or {}
|
||||
metadata["patch_count"] = int(metadata.get("patch_count") or 0) + len(patch_models)
|
||||
version.metadata_json = metadata
|
||||
|
||||
for patch in patch_models:
|
||||
session.add(
|
||||
CvPatch(
|
||||
version_id=version.id,
|
||||
target_path=patch.target_path,
|
||||
operation=patch.operation.value,
|
||||
old_value=patch.old_value,
|
||||
new_value=patch.new_value,
|
||||
metadata_json=patch.metadata,
|
||||
)
|
||||
)
|
||||
|
||||
await session.commit()
|
||||
|
||||
stmt_refresh = (
|
||||
select(CvVersion)
|
||||
.where(CvVersion.id == version_id)
|
||||
.options(selectinload(CvVersion.patches))
|
||||
)
|
||||
result = await session.execute(stmt_refresh)
|
||||
return result.scalars().one()
|
||||
|
||||
|
||||
async def delete_version(
|
||||
session: AsyncSession, owner_id: str, version_id: str
|
||||
) -> bool | str:
|
||||
|
||||
Reference in New Issue
Block a user