Update project files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ANDREW HOSFORD
2026-04-07 11:59:48 -05:00
parent dd227be9b0
commit 0e8978f48c
50 changed files with 5878 additions and 241 deletions

View File

@@ -1,4 +1,4 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Body, Depends, HTTPException
from sqlalchemy import func
from sqlalchemy.orm import Session
@@ -54,11 +54,21 @@ def _next_version(db: Session, req_id: str) -> int:
return (max_ver or 0) + 1
def _auto_summary(update_data: dict) -> str:
fields = [k for k in update_data if k in _SNAPSHOT_FIELDS]
if not fields:
return "Update"
return "Updated " + ", ".join(fields)
def _auto_summary(update_data: dict, node: RequirementNode) -> str:
changed = []
for k in update_data:
if k not in _SNAPSHOT_FIELDS:
continue
old_val = getattr(node, k, None)
new_val = update_data[k]
# Normalize enums for comparison
if hasattr(old_val, "value"):
old_val = old_val.value
if old_val != new_val:
changed.append(k)
if not changed:
return "No changes"
return "Updated " + ", ".join(changed)
def _to_response(node: RequirementNode, include_children: bool = False) -> RequirementResponse:
@@ -150,6 +160,8 @@ def update_requirement(req_id: str, data: RequirementUpdate, db: Session = Depen
raise HTTPException(404, f"Requirement {req_id} not found")
update_data = data.model_dump(exclude_unset=True)
summary = _auto_summary(update_data, node)
for key, value in update_data.items():
setattr(node, key, value)
@@ -158,7 +170,7 @@ def update_requirement(req_id: str, data: RequirementUpdate, db: Session = Depen
node.traceable = quality.traceable
version = _next_version(db, req_id)
_create_history_entry(db, node, version, _auto_summary(update_data))
_create_history_entry(db, node, version, summary)
db.commit()
db.refresh(node)
@@ -166,10 +178,53 @@ def update_requirement(req_id: str, data: RequirementUpdate, db: Session = Depen
@router.delete("/{req_id}", status_code=204)
def delete_requirement(req_id: str, db: Session = Depends(get_db)):
def delete_requirement(
req_id: str,
db: Session = Depends(get_db),
delete_ids: list[str] = Body(default=[]),
):
node = db.get(RequirementNode, req_id)
if not node:
raise HTTPException(404, f"Requirement {req_id} not found")
# Recursively collect all descendants of the IDs marked for deletion
ids_to_delete: set[str] = set(delete_ids)
if ids_to_delete:
queue = list(ids_to_delete)
while queue:
nid = queue.pop()
child_node = db.get(RequirementNode, nid)
if child_node:
for c in child_node.children:
if c.id not in ids_to_delete:
ids_to_delete.add(c.id)
queue.append(c.id)
# Orphan direct children not in the delete set
for child in list(node.children):
if child.id in ids_to_delete:
continue
child.parent_id = None
quality = validate_requirement(child, child.children)
child.traceable = quality.traceable
# Flush so the parent_id = NULL changes are persisted before we delete the parent
db.flush()
# Delete the marked descendants
for did in ids_to_delete:
desc = db.get(RequirementNode, did)
if desc:
# Orphan any grandchildren of a deleted node that aren't themselves being deleted
for grandchild in list(desc.children):
if grandchild.id not in ids_to_delete:
grandchild.parent_id = None
quality = validate_requirement(grandchild, grandchild.children)
grandchild.traceable = quality.traceable
db.flush()
db.delete(desc)
db.flush()
db.delete(node)
db.commit()
@@ -197,7 +252,7 @@ def get_children(req_id: str, db: Session = Depends(get_db)):
node = db.get(RequirementNode, req_id)
if not node:
raise HTTPException(404, f"Requirement {req_id} not found")
return [_to_response(c) for c in node.children]
return [_to_response(c, include_children=True) for c in node.children]
# ─── History Endpoints ────────────────────────────────────────────────────