Drafter reuse-recall (both halves) + the multi-user ACL work it exposed #78

Merged
bvandeusen merged 14 commits from dev into main 2026-07-26 00:25:27 -04:00
3 changed files with 31 additions and 10 deletions
Showing only changes of commit e9cd3435ad - Show all commits
+20 -8
View File
@@ -209,16 +209,28 @@ def notes_visibility_clause(user_id: int, scope: str = "own"):
async def owner_names_for(user_ids: set[int]) -> dict[int, str]:
"""{user_id: username} in one query. Empty input costs nothing."""
"""{user_id: username} in one query. Empty input costs nothing.
Fails soft: on a lookup error the caller gets no names and renders "another
user" instead. Losing an attribution is a cosmetic downgrade, and the part
that matters — that the record ISN'T the caller's — comes from comparing
owner ids, not from this. Failing the whole search over a username would be
the worse trade.
"""
if not user_ids:
return {}
async with async_session() as session:
rows = (
await session.execute(
select(User.id, User.username).where(User.id.in_(user_ids))
)
).all()
return {uid: name for uid, name in rows}
try:
async with async_session() as session:
rows = (
await session.execute(
select(User.id, User.username).where(User.id.in_(user_ids))
)
).all()
return {uid: name for uid, name in rows}
except Exception:
logger.warning("Owner-name lookup failed; falling back to unnamed "
"attribution", exc_info=True)
return {}
def _my_group_ids(user_id: int):
+6 -1
View File
@@ -18,13 +18,18 @@ def _reset_user_ctx():
def _fake_note(*, id: int, title: str, body: str = "",
tags: list[str] | None = None, is_task: bool = False) -> MagicMock:
tags: list[str] | None = None, is_task: bool = False,
user_id: int = 7) -> MagicMock:
note = MagicMock()
note.id = id
note.title = title
note.body = body
note.tags = tags or []
note.is_task = is_task
# A real int, matching the bound caller by default: results compare it to
# decide whether to attach a shared/owner marker, and an auto-MagicMock would
# read as "someone else's" and send the tool looking up a username.
note.user_id = user_id
return note
+5 -1
View File
@@ -10,9 +10,13 @@ def _rule(rid, title, topic_id):
return r
def _note(nid, title):
def _note(nid, title, user_id=1):
n = MagicMock()
n.id, n.title = nid, title
# Real int, defaulting to the caller used in these tests: the injected menu
# compares it to decide whether the line needs a "shared by …" attribution,
# and an auto-MagicMock would read as another user's note.
n.user_id = user_id
return n