Skip to main content

khive_runtime/
note_read.rs

1//! Filtered note reads preserve the caller's visible scope and insertion cursor.
2
3use khive_storage::note::{Note, NoteFilter};
4use khive_storage::{PageRequest, SeekCursor};
5use uuid::Uuid;
6
7use crate::{KhiveRuntime, MailboxView, NamespaceToken, RuntimeError, RuntimeResult};
8
9impl KhiveRuntime {
10    pub async fn list_notes_filtered(
11        &self,
12        token: &NamespaceToken,
13        mut filter: NoteFilter,
14        limit: u32,
15        offset: u32,
16    ) -> RuntimeResult<Vec<Note>> {
17        filter.namespaces = token
18            .visible_namespace_strs()
19            .into_iter()
20            .map(str::to_owned)
21            .collect();
22        Ok(self
23            .notes(token)?
24            .query_notes_filtered_count_free(
25                token.namespace().as_str(),
26                &filter,
27                PageRequest {
28                    limit,
29                    offset: offset.into(),
30                },
31            )
32            .await?
33            .items)
34    }
35
36    pub async fn list_notes_filtered_after(
37        &self,
38        token: &NamespaceToken,
39        mut filter: NoteFilter,
40        after: Option<Uuid>,
41        limit: u32,
42    ) -> RuntimeResult<(Vec<Note>, Option<Uuid>)> {
43        let store = self.notes(token)?;
44        let after = match after {
45            None => None,
46            Some(id) => {
47                let note = self
48                    .get_note_including_deleted(token, id)
49                    .await?
50                    .ok_or_else(|| RuntimeError::NotFound(format!("note cursor {id}")))?;
51                Self::ensure_namespace_visible(&note.namespace, token)?;
52                // A cursor naming a message the mailbox scope hides answers
53                // exactly as a missing cursor does, so it is not an
54                // existence probe.
55                if filter
56                    .mailbox
57                    .as_ref()
58                    .is_some_and(|scope| !MailboxView::scope_permits_message_note(scope, &note))
59                {
60                    return Err(RuntimeError::NotFound(format!("note cursor {id}")));
61                }
62                let sequence = store.note_sequence(id).await?.ok_or_else(|| {
63                    RuntimeError::Internal(format!(
64                        "note cursor {id} has no insertion-sequence ledger row"
65                    ))
66                })?;
67                Some(SeekCursor { sequence, id })
68            }
69        };
70        filter.namespaces = token
71            .visible_namespace_strs()
72            .into_iter()
73            .map(str::to_owned)
74            .collect();
75        let page = store
76            .query_notes_filtered_after(token.namespace().as_str(), &filter, after, limit)
77            .await?;
78        Ok((page.items, page.next_after.map(|cursor| cursor.id)))
79    }
80}