refactor(routes): one supersession seam for REST and MCP; PUT/PATCH notes share a handler; shared mask/not-found/caller helpers (#2829, milestone 296 area 5)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 25s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Failing after 37s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 25s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Failing after 37s
CI & Build / Build & push image (push) Skipped
Reading the 28 route modules against each other and against the MCP tools: - routes/notes.py carried a PUT and a PATCH handler that were the same function minus the supersedes contract on one of them — one handler now serves both verbs, so both carry it. - The two _attach_supersession copies (REST + MCP) become supersession_svc.attach_relations(uid, note_id, data, hint=) — the seam the two surfaces must agree through; only the agent surface adds the one-sentence reading hint. - Three local _uid() wrappers over g.user.id → scribe.auth.get_current_user_id like every other module; design_systems' private _not_found → routes.utils. not_found; the four "********" literals → settings_svc.SECRET_MASK with the read/write contract written once. - routes/plugin.py: the project_id/repo resolution block and the comma-separated id parse were copied into three endpoints — _project_scope() and _int_list() now. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,22 +26,6 @@ from scribe.services import dedup as dedup_svc
|
||||
from scribe.services import supersession as supersession_svc
|
||||
from scribe.services.note_usage import record_pulled
|
||||
|
||||
|
||||
async def _attach_supersession(uid: int, note_id: int, data: dict) -> None:
|
||||
"""Both directions of the supersession relation on a note payload.
|
||||
|
||||
Mirrors the MCP helper of the same name — the two surfaces must agree about
|
||||
what a note's payload says, or the web UI and the agent would disagree about
|
||||
whether a record is current.
|
||||
|
||||
Omitted when empty: a field that always says nothing trains readers to skip
|
||||
fields, which is what `consolidated_at` cost (#2483).
|
||||
"""
|
||||
rel = await supersession_svc.get_relations(uid, note_id)
|
||||
if rel["supersedes"]:
|
||||
data["supersedes"] = rel["supersedes"]
|
||||
if rel["superseded_by"]:
|
||||
data["superseded_by"] = rel["superseded_by"]
|
||||
from scribe.services.note_versions import list_versions, get_version
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -142,7 +126,7 @@ async def create_note_route():
|
||||
# may not write the target. The note itself was created.
|
||||
return jsonify({"error": str(exc), "note": note.to_dict()}), 403
|
||||
out = note.to_dict()
|
||||
await _attach_supersession(uid, note.id, out)
|
||||
await supersession_svc.attach_relations(uid, note.id, out)
|
||||
return jsonify(out), 201
|
||||
|
||||
|
||||
@@ -241,13 +225,17 @@ async def get_note_route(note_id: int):
|
||||
# injected line useful?" is answered by agent pulls alone, and a human
|
||||
# clicking a link would inflate exactly the number #1038 and #2085 gate on.
|
||||
record_pulled(user_id=uid, note_id=note_id, source="rest_note")
|
||||
await _attach_supersession(uid, note_id, data)
|
||||
await supersession_svc.attach_relations(uid, note_id, data)
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@notes_bp.route("/<int:note_id>", methods=["PUT"])
|
||||
@notes_bp.route("/<int:note_id>", methods=["PUT", "PATCH"])
|
||||
@login_required
|
||||
async def update_note_route(note_id: int):
|
||||
"""Partial update — only the keys present in the payload change. PUT and
|
||||
PATCH are the same handler on purpose: the form sends the field set it
|
||||
edited, and the two verbs used to be two near-identical copies of this
|
||||
function that drifted (one carried the supersedes contract, one did not)."""
|
||||
uid = get_current_user_id()
|
||||
# Share-aware: resolve through the ACL and write as the OWNER, so a shared
|
||||
# editor's save isn't rejected by the owner-scoped update service.
|
||||
@@ -290,44 +278,10 @@ async def update_note_route(note_id: int):
|
||||
except PermissionError as exc:
|
||||
return jsonify({"error": str(exc)}), 403
|
||||
out = note.to_dict()
|
||||
await _attach_supersession(uid, note_id, out)
|
||||
await supersession_svc.attach_relations(uid, note_id, out)
|
||||
return jsonify(out)
|
||||
|
||||
|
||||
@notes_bp.route("/<int:note_id>", methods=["PATCH"])
|
||||
@login_required
|
||||
async def patch_note_route(note_id: int):
|
||||
uid = get_current_user_id()
|
||||
result = await get_note_for_user(uid, note_id)
|
||||
if result is None:
|
||||
return not_found("Note")
|
||||
note_obj, _ = result
|
||||
if not await can_write_note(uid, note_id):
|
||||
return jsonify({"error": "Permission denied"}), 403
|
||||
owner_uid = note_obj.user_id
|
||||
data = await request.get_json()
|
||||
fields = {}
|
||||
for key in ("title", "body", "description", "parent_id", "project_id", "milestone_id", "status", "priority", "note_type"):
|
||||
if key in data:
|
||||
fields[key] = data[key]
|
||||
if "due_date" in data:
|
||||
if data["due_date"]:
|
||||
result = parse_iso_date(data["due_date"], "due_date")
|
||||
if isinstance(result, tuple):
|
||||
return result
|
||||
fields["due_date"] = result
|
||||
else:
|
||||
fields["due_date"] = None
|
||||
if "tags" in data:
|
||||
fields["tags"] = data["tags"]
|
||||
try:
|
||||
note = await update_note(owner_uid, note_id, **fields)
|
||||
except ValueError as e:
|
||||
return jsonify({"error": str(e)}), 400
|
||||
if note is None:
|
||||
return not_found("Note")
|
||||
return jsonify(note.to_dict())
|
||||
|
||||
|
||||
@notes_bp.route("/<int:note_id>", methods=["DELETE"])
|
||||
@login_required
|
||||
|
||||
Reference in New Issue
Block a user