mirror of
https://github.com/Xevion/banner.git
synced 2026-01-31 04:23:34 -06:00
feat: implement comprehensive course data model with reference cache and search
This commit is contained in:
@@ -2,16 +2,71 @@
|
||||
|
||||
use crate::banner::BannerApi;
|
||||
use crate::banner::Course;
|
||||
use crate::data::models::ReferenceData;
|
||||
use crate::status::ServiceStatusRegistry;
|
||||
use anyhow::Result;
|
||||
use sqlx::PgPool;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
/// In-memory cache for reference data (code→description lookups).
|
||||
///
|
||||
/// Loaded from the `reference_data` table on startup and refreshed periodically.
|
||||
pub struct ReferenceCache {
|
||||
/// `(category, code)` → `description`
|
||||
data: HashMap<(String, String), String>,
|
||||
}
|
||||
|
||||
impl Default for ReferenceCache {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ReferenceCache {
|
||||
/// Create an empty cache.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build cache from a list of reference data entries.
|
||||
pub fn from_entries(entries: Vec<ReferenceData>) -> Self {
|
||||
let data = entries
|
||||
.into_iter()
|
||||
.map(|e| ((e.category, e.code), e.description))
|
||||
.collect();
|
||||
Self { data }
|
||||
}
|
||||
|
||||
/// Look up a description by category and code.
|
||||
pub fn lookup(&self, category: &str, code: &str) -> Option<&str> {
|
||||
self.data
|
||||
.get(&(category.to_string(), code.to_string()))
|
||||
.map(|s| s.as_str())
|
||||
}
|
||||
|
||||
/// Get all `(code, description)` pairs for a category, sorted by description.
|
||||
pub fn entries_for_category(&self, category: &str) -> Vec<(&str, &str)> {
|
||||
let mut entries: Vec<(&str, &str)> = self
|
||||
.data
|
||||
.iter()
|
||||
.filter(|((cat, _), _)| cat == category)
|
||||
.map(|((_, code), desc)| (code.as_str(), desc.as_str()))
|
||||
.collect();
|
||||
entries.sort_by(|a, b| a.1.cmp(b.1));
|
||||
entries
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub banner_api: Arc<BannerApi>,
|
||||
pub db_pool: PgPool,
|
||||
pub service_statuses: ServiceStatusRegistry,
|
||||
pub reference_cache: Arc<RwLock<ReferenceCache>>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@@ -20,9 +75,20 @@ impl AppState {
|
||||
banner_api,
|
||||
db_pool,
|
||||
service_statuses: ServiceStatusRegistry::new(),
|
||||
reference_cache: Arc::new(RwLock::new(ReferenceCache::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the reference cache from the database.
|
||||
pub async fn load_reference_cache(&self) -> Result<()> {
|
||||
let entries = crate::data::reference::get_all(&self.db_pool).await?;
|
||||
let count = entries.len();
|
||||
let cache = ReferenceCache::from_entries(entries);
|
||||
*self.reference_cache.write().await = cache;
|
||||
tracing::info!(entries = count, "Reference cache loaded");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a course by CRN directly from Banner API
|
||||
pub async fn get_course_or_fetch(&self, term: &str, crn: &str) -> Result<Course> {
|
||||
self.banner_api
|
||||
|
||||
Reference in New Issue
Block a user