diff --git a/src/fabledassistant/services/knowledge.py b/src/fabledassistant/services/knowledge.py index 753a9c9..1e0e48b 100644 --- a/src/fabledassistant/services/knowledge.py +++ b/src/fabledassistant/services/knowledge.py @@ -68,7 +68,10 @@ def _apply_type_filter(stmt, note_type: str | None): 'task' = any task (status not null); 'plan' = a task with task_kind='plan'; any other non-empty type = a non-task note of that note_type; None = all. + + Trashed rows (deleted_at set) are always excluded. """ + stmt = stmt.where(Note.deleted_at.is_(None)) if note_type == "task": return stmt.where(Note.status.isnot(None)) if note_type == "plan": @@ -173,6 +176,8 @@ async def _semantic_knowledge_search( is_task=is_task_filter, ) for _score, note in candidates: + if note.deleted_at is not None: + continue if note_type == "task" and not note.is_task: continue elif note_type == "plan" and (not note.is_task or note.task_kind != "plan"): @@ -223,6 +228,7 @@ async def get_knowledge_counts(user_id: int, tags: list[str] | None = None) -> d select(Note.note_type, func.count(Note.id)) .where(Note.user_id == user_id) .where(Note.status.is_(None)) + .where(Note.deleted_at.is_(None)) .where(Note.note_type.in_(["note", "person", "place", "list"])) .group_by(Note.note_type) ) @@ -237,6 +243,7 @@ async def get_knowledge_counts(user_id: int, tags: list[str] | None = None) -> d select(func.count(Note.id)) .where(Note.user_id == user_id) .where(Note.status.isnot(None)) + .where(Note.deleted_at.is_(None)) ) if tags: for tag in tags: @@ -251,6 +258,7 @@ async def get_knowledge_counts(user_id: int, tags: list[str] | None = None) -> d .where(Note.user_id == user_id) .where(Note.status.isnot(None)) .where(Note.task_kind == "plan") + .where(Note.deleted_at.is_(None)) ) if tags: for tag in tags: @@ -314,6 +322,7 @@ async def get_knowledge_by_ids(user_id: int, ids: list[int]) -> list[dict]: select(Note) .where(Note.user_id == user_id) .where(Note.id.in_(ids)) + .where(Note.deleted_at.is_(None)) ) rows = list((await session.execute(stmt)).scalars().all()) by_id = {n.id: n for n in rows} @@ -328,6 +337,7 @@ async def get_people_and_places_context(user_id: int) -> str: .where(Note.user_id == user_id) .where(Note.note_type.in_(["person", "place"])) .where(Note.status.is_(None)) + .where(Note.deleted_at.is_(None)) .order_by(Note.title.asc()) .limit(50) ) diff --git a/src/fabledassistant/services/notes.py b/src/fabledassistant/services/notes.py index 4ef525f..907d1da 100644 --- a/src/fabledassistant/services/notes.py +++ b/src/fabledassistant/services/notes.py @@ -108,7 +108,9 @@ async def create_note( async def get_note(user_id: int, note_id: int) -> Note | None: async with async_session() as session: result = await session.execute( - select(Note).where(Note.id == note_id, Note.user_id == user_id) + select(Note).where( + Note.id == note_id, Note.user_id == user_id, Note.deleted_at.is_(None) + ) ) return result.scalars().first() @@ -135,8 +137,10 @@ async def list_notes( offset: int = 0, ) -> tuple[list[Note], int]: async with async_session() as session: - query = select(Note).where(Note.user_id == user_id) - count_query = select(func.count(Note.id)).where(Note.user_id == user_id) + query = select(Note).where(Note.user_id == user_id, Note.deleted_at.is_(None)) + count_query = select(func.count(Note.id)).where( + Note.user_id == user_id, Note.deleted_at.is_(None) + ) # Filter by task vs note if is_task is True: @@ -239,6 +243,7 @@ async def get_note_by_title(user_id: int, title: str) -> Note | None: select(Note).where( Note.user_id == user_id, func.lower(Note.title) == func.lower(title.strip()), + Note.deleted_at.is_(None), ).limit(1) ) return result.scalars().first() @@ -402,7 +407,9 @@ async def search_notes_for_context( pattern = f"%{escaped}%" keyword_filters.append(or_(Note.title.ilike(pattern), Note.body.ilike(pattern))) - query = select(Note).where(Note.user_id == user_id, or_(*keyword_filters)) + query = select(Note).where( + Note.user_id == user_id, or_(*keyword_filters), Note.deleted_at.is_(None) + ) if orphan_only: query = query.where(Note.project_id.is_(None)) elif project_id is not None: @@ -421,7 +428,9 @@ async def get_notes_by_ids(user_id: int, note_ids: list[int]) -> dict[int, Note] return {} async with async_session() as session: result = await session.execute( - select(Note).where(Note.user_id == user_id, Note.id.in_(note_ids)) + select(Note).where( + Note.user_id == user_id, Note.id.in_(note_ids), Note.deleted_at.is_(None) + ) ) return {n.id: n for n in result.scalars().all()} @@ -444,6 +453,7 @@ async def get_backlinks(user_id: int, note_id: int) -> list[dict]: select(Note.id, Note.title, Note.status).where( Note.user_id == user_id, Note.id != note_id, + Note.deleted_at.is_(None), or_(Note.body.like(pattern), Note.body.like(pattern_alias)), ) )