Save 1C access profile drafts
CI / python (push) Has been cancelled
CI / rust (push) Has been cancelled

This commit is contained in:
2026-05-21 18:27:20 +03:00
parent feaf40c205
commit db5fdf0aa4
2 changed files with 96 additions and 0 deletions
@@ -194,6 +194,8 @@ from operations_core import (
build_project_report,
)
from one_c_normalizer import (
AccessProfile,
AccessRoleAssignment,
COMMON_BRANCH_CHILDREN,
METADATA_CHILD_OBJECT_SPECS,
METADATA_TYPE_CONTEXT_ACTIONS,
@@ -853,6 +855,19 @@ class AccessProfileDraftResponse(BaseModel):
proposed_profile: dict = Field(default_factory=dict)
class AccessProfileApplyRequest(AccessProfileDraftRequest):
allow_incomplete: bool = False
replace_existing: bool = False
author: str | None = None
class AccessProfileApplyResponse(BaseModel):
applied: bool
profile: dict
draft: AccessProfileDraftResponse
message: str
class ProjectSetupResponse(BaseModel):
project_id: str
status: ProjectSetupStatus
@@ -2948,6 +2963,39 @@ async def preview_project_access_profile(project_id: str, request: AccessProfile
return _build_access_profile_draft(normalized, request)
@app.post("/projects/{project_id}/access/profiles", response_model=AccessProfileApplyResponse)
async def apply_project_access_profile(project_id: str, request: AccessProfileApplyRequest) -> AccessProfileApplyResponse:
normalized = _load_normalized_project(project_id)
if normalized is None:
raise HTTPException(status_code=404, detail="NormalizedProject not found")
draft = _build_access_profile_draft(normalized, request)
if draft.missing_objects and not request.allow_incomplete:
raise HTTPException(status_code=409, detail="Access profile draft has missing objects")
profile = _access_profile_from_draft(draft, request)
existing_index = next(
(
index
for index, item in enumerate(normalized.access.profiles)
if item.name.casefold() == profile.name.casefold()
or str(item.qualified_name or "").casefold() == str(profile.qualified_name or "").casefold()
),
None,
)
if existing_index is not None and not request.replace_existing:
raise HTTPException(status_code=409, detail="Access profile already exists")
if existing_index is None:
normalized.access.profiles.append(profile)
else:
normalized.access.profiles[existing_index] = profile
_save_normalized_project(project_id, normalized)
return AccessProfileApplyResponse(
applied=True,
profile=profile.model_dump(mode="json"),
draft=draft,
message="Access profile saved in SFERA workspace model",
)
@app.get("/projects/{project_id}/imports/quality", response_model=ImportQualityResponse)
async def get_import_quality(project_id: str) -> ImportQualityResponse:
return _import_quality_response(project_id)
@@ -7374,6 +7422,38 @@ def _build_access_profile_draft(
)
def _access_profile_from_draft(
draft: AccessProfileDraftResponse,
request: AccessProfileApplyRequest,
) -> AccessProfile:
now = _current_timestamp()
attributes = {
"status": "workspace_draft",
"created_by": request.author or "",
"created_at": now,
"target_objects": list(draft.proposed_profile.get("target_objects", [])),
"permissions": list(draft.proposed_profile.get("permissions", [])),
"missing_objects": list(draft.missing_objects),
}
return AccessProfile(
name=draft.name,
qualified_name=str(draft.proposed_profile.get("qualified_name") or f"ПрофильГруппыДоступа.{draft.name}"),
attributes=attributes,
roles=[
AccessRoleAssignment(
role=role.role,
role_qualified_name=role.role_qualified_name,
source=role.source,
attributes={
"matched_objects": role.matched_objects,
"permissions": role.permissions,
},
)
for role in draft.roles
],
)
def _access_roles_for_targets(
normalized: NormalizedProject,
target_objects: list[str],