mirror of
https://github.com/thegeneralist01/archivr
synced 2026-07-21 18:55:36 +02:00
feat(collections): collection CRUD + entry-visibility routes
This commit is contained in:
parent
02cab149fa
commit
24c5321f6e
3 changed files with 257 additions and 6 deletions
|
|
@ -392,6 +392,45 @@ pub fn get_entry_collections(
|
|||
))
|
||||
}
|
||||
|
||||
/// Returns entries belonging to a collection, filtered by caller_bits visibility.
|
||||
/// Caller with admin/owner bits (4|8) sees all entries regardless of visibility_bits.
|
||||
pub fn list_entries_for_collection(
|
||||
conn: &rusqlite::Connection,
|
||||
collection_id: i64,
|
||||
caller_bits: u32,
|
||||
) -> Result<Vec<EntrySummary>> {
|
||||
let sql = format!(
|
||||
"{} {} \
|
||||
JOIN collection_entries ce ON ce.entry_id = e.id \
|
||||
WHERE ce.collection_id = ?1 \
|
||||
AND (CAST(?2 AS INTEGER) & 12 != 0 \
|
||||
OR (ce.visibility_bits & CAST(?2 AS INTEGER)) != 0) \
|
||||
GROUP BY e.id \
|
||||
ORDER BY e.archived_at DESC, e.id DESC",
|
||||
ENTRY_SELECT_COLS,
|
||||
ENTRY_FROM_JOINS,
|
||||
);
|
||||
let mut stmt = conn.prepare(&sql)?;
|
||||
let entries = stmt
|
||||
.query_map([collection_id, caller_bits as i64], |row| {
|
||||
Ok(EntrySummary {
|
||||
entry_uid: row.get(0)?,
|
||||
archived_at: row.get(1)?,
|
||||
source_kind: row.get(2)?,
|
||||
entity_kind: row.get(3)?,
|
||||
title: row.get(4)?,
|
||||
visibility: row.get(5)?,
|
||||
original_url: row.get(6)?,
|
||||
artifact_count: row.get(7)?,
|
||||
total_artifact_bytes: row.get(8)?,
|
||||
parent_entry_uid: row.get(9)?,
|
||||
has_favicon: row.get::<_, i64>(10)? != 0,
|
||||
})
|
||||
})?
|
||||
.collect::<rusqlite::Result<Vec<_>>>()?;
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
/// Resolves an artifact to its absolute on-disk path under `store_path`.
|
||||
///
|
||||
/// `artifact.relpath` is a store-relative path (e.g. `raw/a/b/abc.pdf`).
|
||||
|
|
@ -417,7 +456,7 @@ pub fn resolve_artifact_path(
|
|||
Ok(canonical_artifact)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SearchEntriesQuery {
|
||||
/// Free-text term: LIKE-matched against title, canonical_url, entry_uid, source_kind, entity_kind, visibility
|
||||
pub q: Option<String>,
|
||||
|
|
@ -440,6 +479,22 @@ pub struct SearchEntriesQuery {
|
|||
pub caller_bits: u32,
|
||||
}
|
||||
|
||||
impl Default for SearchEntriesQuery {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
q: None,
|
||||
source_kind: None,
|
||||
entity_kind: None,
|
||||
url: None,
|
||||
title: None,
|
||||
after: None,
|
||||
before: None,
|
||||
tag: None,
|
||||
caller_bits: u32::MAX,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a raw search string into a [`SearchEntriesQuery`].
|
||||
///
|
||||
/// Recognized prefixes: `source:`, `type:`, `url:`, `title:`, `after:`, `before:`, `tag:`.
|
||||
|
|
@ -928,7 +983,7 @@ mod tests {
|
|||
database::complete_archive_run_item(&conn, item.id, entry.id).unwrap();
|
||||
database::finish_archive_run(&conn, run.id).unwrap();
|
||||
|
||||
let entries = list_root_entries(&conn).unwrap();
|
||||
let entries = list_root_entries(&conn, u32::MAX).unwrap();
|
||||
assert_eq!(entries.len(), 1);
|
||||
assert_eq!(entries[0].title.as_deref(), Some("Saved Article"));
|
||||
assert_eq!(entries[0].artifact_count, 1);
|
||||
|
|
@ -1099,7 +1154,7 @@ mod tests {
|
|||
#[test]
|
||||
fn search_empty_query_returns_all_root_entries() {
|
||||
let conn = make_test_db_with_entries();
|
||||
let all = list_root_entries(&conn).unwrap();
|
||||
let all = list_root_entries(&conn, u32::MAX).unwrap();
|
||||
let searched = search_entries(&conn, &SearchEntriesQuery::default()).unwrap();
|
||||
assert_eq!(all.len(), searched.len());
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue