diff options
author | metamuffin <metamuffin@disroot.org> | 2025-02-02 23:16:59 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-02-02 23:16:59 +0100 |
commit | e3daa6159f2b2048c2c07d349488e117e50285dd (patch) | |
tree | 4d2d2fc24fba0a3516b852a806cc7e14dd0f2b58 | |
parent | c4d40a34be067872e8b6f59520ab9da8d89b70e0 (diff) | |
download | jellything-e3daa6159f2b2048c2c07d349488e117e50285dd.tar jellything-e3daa6159f2b2048c2c07d349488e117e50285dd.tar.bz2 jellything-e3daa6159f2b2048c2c07d349488e117e50285dd.tar.zst |
restore search functionality
-rw-r--r-- | base/src/database.rs | 45 | ||||
-rw-r--r-- | server/src/routes/mod.rs | 3 | ||||
-rw-r--r-- | server/src/routes/ui/admin/mod.rs | 24 | ||||
-rw-r--r-- | server/src/routes/ui/search.rs | 4 |
4 files changed, 63 insertions, 13 deletions
diff --git a/base/src/database.rs b/base/src/database.rs index 28cafaa..3c8bef4 100644 --- a/base/src/database.rs +++ b/base/src/database.rs @@ -19,9 +19,10 @@ use std::{ use tantivy::{ collector::{Count, TopDocs}, directory::MmapDirectory, + doc, query::QueryParser, schema::{Field, Schema, Value, FAST, INDEXED, STORED, STRING, TEXT}, - DateOptions, Index, IndexReader, IndexWriter, ReloadPolicy, TantivyDocument, + DateOptions, DateTime, Index, IndexReader, IndexWriter, ReloadPolicy, TantivyDocument, }; const T_USER: TableDefinition<&str, Ser<User>> = TableDefinition::new("user"); @@ -38,7 +39,7 @@ const T_IMPORT_FILE_MTIME: TableDefinition<&[u8], u64> = TableDefinition::new("i #[derive(Clone)] pub struct Database { inner: Arc<redb::Database>, - node_index: Arc<NodeIndex>, + text_search: Arc<NodeTextSearchIndex>, } impl Database { @@ -47,10 +48,10 @@ impl Database { info!("opening kv store..."); let db = redb::Database::create(path.join("data")).context("opening kv store")?; info!("opening node index..."); - let ft_node = NodeIndex::new(path).context("in node index")?; + let ft_node = NodeTextSearchIndex::new(path).context("in node index")?; let r = Self { inner: db.into(), - node_index: ft_node.into(), + text_search: ft_node.into(), }; { @@ -287,13 +288,13 @@ impl Database { } pub fn search(&self, query: &str, page: usize) -> Result<(usize, Vec<NodeID>)> { let query = QueryParser::for_index( - &self.node_index.index, - vec![self.node_index.title, self.node_index.description], + &self.text_search.index, + vec![self.text_search.title, self.text_search.description], ) .parse_query(query) .context("parsing query")?; - let searcher = self.node_index.reader.searcher(); + let searcher = self.text_search.reader.searcher(); let sres = searcher.search(&query, &TopDocs::with_limit(32).and_offset(page * 32))?; let scount = searcher.search(&query, &Count)?; @@ -301,7 +302,7 @@ impl Database { for (_, daddr) in sres { let doc: TantivyDocument = searcher.doc(daddr)?; let id = doc - .get_first(self.node_index.id) + .get_first(self.text_search.id) .unwrap() .as_bytes() .unwrap(); @@ -310,6 +311,30 @@ impl Database { } Ok((scount, results)) } + + pub fn search_create_index(&self) -> Result<()> { + let mut w = self.text_search.writer.write().unwrap(); + w.delete_all_documents()?; + + let txn = self.inner.begin_read()?; + let nodes = txn.open_table(T_NODE)?; + for node in nodes.iter()? { + let (x, y) = node?; + let (id, node) = (x.value().to_owned(), y.value().0); + + w.add_document(doc!( + self.text_search.id => id.to_vec(), + self.text_search.title => node.title.unwrap_or_default(), + self.text_search.description => node.description.unwrap_or_default(), + self.text_search.releasedate => DateTime::from_timestamp_millis(node.release_date.unwrap_or_default()), + self.text_search.f_index => node.index.unwrap_or_default() as u64, + ))?; + } + + w.commit()?; + Ok(()) + } + pub fn create_admin_user(&self, username: &str, password_hash: Vec<u8>) -> Result<()> { let txn = self.inner.begin_write().unwrap(); let mut users = txn.open_table(T_USER).unwrap(); @@ -355,7 +380,7 @@ impl Database { } } -pub struct NodeIndex { +pub struct NodeTextSearchIndex { pub schema: Schema, pub reader: IndexReader, pub writer: RwLock<IndexWriter>, @@ -367,7 +392,7 @@ pub struct NodeIndex { pub parent: Field, pub f_index: Field, } -impl NodeIndex { +impl NodeTextSearchIndex { fn new(path: &Path) -> anyhow::Result<Self> { let mut schema = Schema::builder(); let id = schema.add_text_field("id", TEXT | STORED | FAST); diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index 5fb9b26..b7d63da 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -32,7 +32,7 @@ use ui::{ admin::{ log::r_admin_log, r_admin_dashboard, r_admin_delete_cache, r_admin_import, r_admin_invite, - r_admin_remove_invite, r_admin_transcode_posters, + r_admin_remove_invite, r_admin_transcode_posters, r_admin_update_search, user::{r_admin_remove_user, r_admin_user, r_admin_user_permission, r_admin_users}, }, assets::{r_asset, r_item_backdrop, r_item_poster, r_node_thumbnail, r_person_asset}, @@ -136,6 +136,7 @@ pub fn build_rocket(database: Database, federation: Federation) -> Rocket<Build> r_admin_transcode_posters, r_admin_log, r_admin_import, + r_admin_update_search, r_account_settings, r_account_settings_post, r_api_version, diff --git a/server/src/routes/ui/admin/mod.rs b/server/src/routes/ui/admin/mod.rs index 50faa2e..62a06bc 100644 --- a/server/src/routes/ui/admin/mod.rs +++ b/server/src/routes/ui/admin/mod.rs @@ -26,7 +26,7 @@ use markup::DynRender; use rand::Rng; use rocket::{form::Form, get, post, FromForm, State}; use std::time::Instant; -use tokio::sync::Semaphore; +use tokio::{sync::Semaphore, task::spawn_blocking}; use user::rocket_uri_macro_r_admin_users; #[get("/admin/dashboard")] @@ -85,6 +85,9 @@ pub async fn admin_dashboard<'a>( form[method="POST", action=uri!(r_admin_transcode_posters())] { input[type="submit", disabled=is_transcoding(), value="Transcode all posters with low resolution"]; } + form[method="POST", action=uri!(r_admin_update_search())] { + input[type="submit", value="Update full-text search index"]; + } form[method="POST", action=uri!(r_admin_delete_cache())] { input.danger[type="submit", value="Delete Cache"]; } @@ -163,6 +166,25 @@ pub async fn r_admin_import( admin_dashboard(database, Some(flash)).await } +#[post("/admin/update_search")] +pub async fn r_admin_update_search( + _session: AdminSession, + database: &State<Database>, +) -> MyResult<DynLayoutPage<'static>> { + let db2 = (*database).clone(); + let r = spawn_blocking(move || db2.search_create_index()) + .await + .unwrap(); + admin_dashboard( + &database, + Some( + r.map_err(|e| e.into()) + .map(|_| format!("Search index updated")), + ), + ) + .await +} + #[post("/admin/delete_cache")] pub async fn r_admin_delete_cache( session: AdminSession, diff --git a/server/src/routes/ui/search.rs b/server/src/routes/ui/search.rs index ac37b80..6b1504f 100644 --- a/server/src/routes/ui/search.rs +++ b/server/src/routes/ui/search.rs @@ -5,6 +5,7 @@ use super::{ node::{DatabaseNodeUserDataExt, NodeCard}, }; use jellybase::database::Database; +use jellycommon::Visibility; use rocket::{get, State}; use std::time::Instant; @@ -18,10 +19,11 @@ pub async fn r_search<'a>( let timing = Instant::now(); let results = if let Some(query) = query { let (count, ids) = db.search(query, page.unwrap_or_default())?; - let nodes = ids + let mut nodes = ids .into_iter() .map(|id| db.get_node_with_userdata(id, &session)) .collect::<Result<Vec<_>, anyhow::Error>>()?; + nodes.retain(|(n, _)| n.visibility >= Visibility::Reduced); Some((count, nodes)) } else { None |