aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-02-02 23:16:59 +0100
committermetamuffin <metamuffin@disroot.org>2025-02-02 23:16:59 +0100
commite3daa6159f2b2048c2c07d349488e117e50285dd (patch)
tree4d2d2fc24fba0a3516b852a806cc7e14dd0f2b58
parentc4d40a34be067872e8b6f59520ab9da8d89b70e0 (diff)
downloadjellything-e3daa6159f2b2048c2c07d349488e117e50285dd.tar
jellything-e3daa6159f2b2048c2c07d349488e117e50285dd.tar.bz2
jellything-e3daa6159f2b2048c2c07d349488e117e50285dd.tar.zst
restore search functionality
-rw-r--r--base/src/database.rs45
-rw-r--r--server/src/routes/mod.rs3
-rw-r--r--server/src/routes/ui/admin/mod.rs24
-rw-r--r--server/src/routes/ui/search.rs4
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