diff --git a/src/db/mod.rs b/src/db/mod.rs index 9cc40b9..e606fd8 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,9 +1,11 @@ pub mod models; pub mod schema; +use std::cmp; use std::env; use anyhow::{Context, Result}; +use diesel::OptionalExtension; use diesel::prelude::*; use diesel::r2d2::{self, ConnectionManager}; use diesel::sqlite::SqliteConnection; @@ -105,3 +107,98 @@ pub fn load_packages_via_pool(pool: &Pool) -> Result> { let mut conn = pool.get().context("acquiring database connection")?; load_packages(&mut conn) } + +/// Load package definitions instead of raw Diesel models for convenience. +pub fn load_package_definitions(conn: &mut SqliteConnection) -> Result> { + load_packages(conn)? + .into_iter() + .map(|record| record.into_definition()) + .collect::>>() +} + +/// Pool-backed helper mirroring [`load_package_definitions`]. +pub fn load_package_definitions_via_pool(pool: &Pool) -> Result> { + let mut conn = pool.get().context("acquiring database connection")?; + load_package_definitions(&mut conn) +} + +/// Locate a package by name and optional version, returning the newest matching entry when +/// the version is not supplied. +pub fn find_package( + conn: &mut SqliteConnection, + name: &str, + version: Option<&str>, +) -> Result> { + let mut query = packages_dsl::packages + .filter(packages_dsl::name.eq(name)) + .into_boxed(); + + if let Some(version) = version { + query = query.filter(packages_dsl::version.eq(version)); + } + + query + .order(packages_dsl::version.desc()) + .first::(conn) + .optional() + .context("querying package by name") +} + +/// Convenience wrapper returning the package as a [`PackageDefinition`]. +pub fn find_package_definition( + conn: &mut SqliteConnection, + name: &str, + version: Option<&str>, +) -> Result> { + Ok(find_package(conn, name, version)? + .map(|pkg| pkg.into_definition()) + .transpose()?) +} + +/// Pool-backed variant of [`find_package_definition`]. +pub fn find_package_definition_via_pool( + pool: &Pool, + name: &str, + version: Option<&str>, +) -> Result> { + let mut conn = pool.get().context("acquiring database connection")?; + find_package_definition(&mut conn, name, version) +} + +/// Locate packages using a basic substring match on the name, ordered deterministically and +/// optionally limited for responsiveness. +pub fn search_packages( + conn: &mut SqliteConnection, + term: &str, + limit: Option, +) -> Result> { + let trimmed = term.trim(); + if trimmed.is_empty() { + return Ok(Vec::new()); + } + + let normalized: String = trimmed.chars().take(128).collect(); + let sanitized = normalized.replace('%', "\\%").replace('_', "\\_"); + let pattern = format!("%{}%", sanitized); + let mut query = packages_dsl::packages + .filter(packages_dsl::name.like(&pattern)) + .order((packages_dsl::name, packages_dsl::version)) + .into_boxed(); + + let effective_limit = limit.map(|value| cmp::max(1, value)).unwrap_or(50); + query = query.limit(cmp::min(effective_limit, 200)); + + query + .load::(conn) + .context("searching packages by name") +} + +/// Pool-backed variant of [`search_packages`]. +pub fn search_packages_via_pool( + pool: &Pool, + term: &str, + limit: Option, +) -> Result> { + let mut conn = pool.get().context("acquiring database connection")?; + search_packages(&mut conn, term, limit) +} diff --git a/src/lib.rs b/src/lib.rs index e28f156..732597d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ pub mod ai; pub mod db; +#[cfg(feature = "graphql")] +pub mod graphql; pub mod html; pub mod ingest; pub mod md5_utils; diff --git a/src/tui/mod.rs b/src/tui/mod.rs index ad84ace..c371676 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -1,3 +1,4 @@ +pub mod animations; pub mod disk_manager; pub mod downloader; pub mod main_menu;