mirror of
https://github.com/Xevion/banner.git
synced 2026-01-30 22:23:32 -06:00
refactor: extract theme toggle styles to CSS and improve timeout handling
This commit is contained in:
@@ -298,7 +298,7 @@ impl DateRange {
|
||||
/// Get the number of weeks between start and end dates
|
||||
pub fn weeks_duration(&self) -> u32 {
|
||||
let duration = self.end.signed_duration_since(self.start);
|
||||
duration.num_weeks() as u32
|
||||
duration.num_weeks().max(0) as u32
|
||||
}
|
||||
|
||||
/// Check if a specific date falls within this range
|
||||
|
||||
+2
-1
@@ -168,8 +168,9 @@ impl SearchQuery {
|
||||
}
|
||||
|
||||
/// Sets the maximum number of results to return
|
||||
/// Clamped to a maximum of 500 to prevent excessive API load
|
||||
pub fn max_results(mut self, max_results: i32) -> Self {
|
||||
self.max_results = max_results;
|
||||
self.max_results = max_results.clamp(1, 500);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
+15
-17
@@ -7,13 +7,12 @@ use cookie::Cookie;
|
||||
use dashmap::DashMap;
|
||||
use governor::state::InMemoryState;
|
||||
use governor::{Quota, RateLimiter};
|
||||
use once_cell::sync::Lazy;
|
||||
use rand::distr::{Alphanumeric, SampleString};
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::{Mutex, Notify};
|
||||
use tracing::{debug, info, trace};
|
||||
@@ -23,9 +22,9 @@ const SESSION_EXPIRY: Duration = Duration::from_secs(25 * 60); // 25 minutes
|
||||
|
||||
// A global rate limiter to ensure we only try to create one new session every 10 seconds,
|
||||
// preventing us from overwhelming the server with session creation requests.
|
||||
static SESSION_CREATION_RATE_LIMITER: Lazy<
|
||||
static SESSION_CREATION_RATE_LIMITER: LazyLock<
|
||||
RateLimiter<governor::state::direct::NotKeyed, InMemoryState, governor::clock::DefaultClock>,
|
||||
> = Lazy::new(|| RateLimiter::direct(Quota::with_period(Duration::from_secs(10)).unwrap()));
|
||||
> = LazyLock::new(|| RateLimiter::direct(Quota::with_period(Duration::from_secs(10)).unwrap()));
|
||||
|
||||
/// Represents an active anonymous session within the Banner API.
|
||||
/// Identified by multiple persistent cookies, as well as a client-generated "unique session ID".
|
||||
@@ -63,16 +62,16 @@ pub fn nonce() -> String {
|
||||
|
||||
impl BannerSession {
|
||||
/// Creates a new session
|
||||
pub fn new(unique_session_id: &str, jsessionid: &str, ssb_cookie: &str) -> Result<Self> {
|
||||
pub fn new(unique_session_id: &str, jsessionid: &str, ssb_cookie: &str) -> Self {
|
||||
let now = Instant::now();
|
||||
|
||||
Ok(Self {
|
||||
Self {
|
||||
created_at: now,
|
||||
last_activity: None,
|
||||
unique_session_id: unique_session_id.to_string(),
|
||||
jsessionid: jsessionid.to_string(),
|
||||
ssb_cookie: ssb_cookie.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the unique session ID
|
||||
@@ -124,47 +123,46 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_new_session_returns_ok() {
|
||||
fn test_new_session_creates_session() {
|
||||
let session = BannerSession::new("sess-1", "JSID123", "SSB456");
|
||||
assert!(session.is_ok());
|
||||
assert_eq!(session.unwrap().id(), "sess-1");
|
||||
assert_eq!(session.id(), "sess-1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fresh_session_not_expired() {
|
||||
let session = BannerSession::new("sess-1", "JSID123", "SSB456").unwrap();
|
||||
let session = BannerSession::new("sess-1", "JSID123", "SSB456");
|
||||
assert!(!session.is_expired());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fresh_session_not_been_used() {
|
||||
let session = BannerSession::new("sess-1", "JSID123", "SSB456").unwrap();
|
||||
let session = BannerSession::new("sess-1", "JSID123", "SSB456");
|
||||
assert!(!session.been_used());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_marks_used() {
|
||||
let mut session = BannerSession::new("sess-1", "JSID123", "SSB456").unwrap();
|
||||
let mut session = BannerSession::new("sess-1", "JSID123", "SSB456");
|
||||
session.touch();
|
||||
assert!(session.been_used());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touched_session_not_expired() {
|
||||
let mut session = BannerSession::new("sess-1", "JSID123", "SSB456").unwrap();
|
||||
let mut session = BannerSession::new("sess-1", "JSID123", "SSB456");
|
||||
session.touch();
|
||||
assert!(!session.is_expired());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cookie_format() {
|
||||
let session = BannerSession::new("sess-1", "JSID123", "SSB456").unwrap();
|
||||
let session = BannerSession::new("sess-1", "JSID123", "SSB456");
|
||||
assert_eq!(session.cookie(), "JSESSIONID=JSID123; SSB_COOKIE=SSB456");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_id_returns_unique_session_id() {
|
||||
let session = BannerSession::new("my-unique-id", "JSID123", "SSB456").unwrap();
|
||||
let session = BannerSession::new("my-unique-id", "JSID123", "SSB456");
|
||||
assert_eq!(session.id(), "my-unique-id");
|
||||
}
|
||||
|
||||
@@ -454,7 +452,7 @@ impl SessionPool {
|
||||
self.select_term(&term.to_string(), &unique_session_id, &cookie_header)
|
||||
.await?;
|
||||
|
||||
BannerSession::new(&unique_session_id, jsessionid, ssb_cookie)
|
||||
Ok(BannerSession::new(&unique_session_id, jsessionid, ssb_cookie))
|
||||
}
|
||||
|
||||
/// Retrieves a list of terms from the Banner API.
|
||||
|
||||
+1
-1
@@ -2,5 +2,5 @@
|
||||
|
||||
/// Returns a browser-like user agent string.
|
||||
pub fn user_agent() -> &'static str {
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
}
|
||||
|
||||
+10
-44
@@ -5,58 +5,24 @@ use crate::data::models::TargetType;
|
||||
use crate::error::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use std::fmt;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that can occur during job parsing
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum JobParseError {
|
||||
InvalidJson(serde_json::Error),
|
||||
#[error("Invalid JSON in job payload: {0}")]
|
||||
InvalidJson(#[from] serde_json::Error),
|
||||
#[error("Unsupported target type: {0:?}")]
|
||||
UnsupportedTargetType(TargetType),
|
||||
}
|
||||
|
||||
impl fmt::Display for JobParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
JobParseError::InvalidJson(e) => write!(f, "Invalid JSON in job payload: {}", e),
|
||||
JobParseError::UnsupportedTargetType(t) => {
|
||||
write!(f, "Unsupported target type: {:?}", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for JobParseError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
JobParseError::InvalidJson(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur during job processing
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum JobError {
|
||||
Recoverable(anyhow::Error), // API failures, network issues
|
||||
Unrecoverable(anyhow::Error), // Parse errors, corrupted data
|
||||
}
|
||||
|
||||
impl fmt::Display for JobError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
JobError::Recoverable(e) => write!(f, "Recoverable error: {}", e),
|
||||
JobError::Unrecoverable(e) => write!(f, "Unrecoverable error: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for JobError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
JobError::Recoverable(e) => e.source(),
|
||||
JobError::Unrecoverable(e) => e.source(),
|
||||
}
|
||||
}
|
||||
#[error("Recoverable error: {0}")]
|
||||
Recoverable(#[source] anyhow::Error),
|
||||
#[error("Unrecoverable error: {0}")]
|
||||
Unrecoverable(#[source] anyhow::Error),
|
||||
}
|
||||
|
||||
/// Common trait interface for all job types
|
||||
|
||||
+2
-2
@@ -4,10 +4,10 @@
|
||||
//! at compile time using rust-embed.
|
||||
|
||||
use dashmap::DashMap;
|
||||
use once_cell::sync::Lazy;
|
||||
use rapidhash::v3::rapidhash_v3;
|
||||
use rust_embed::RustEmbed;
|
||||
use std::fmt;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
/// Embedded web assets from the dist directory
|
||||
#[derive(RustEmbed)]
|
||||
@@ -65,7 +65,7 @@ impl AssetMetadata {
|
||||
}
|
||||
|
||||
/// Global cache for asset metadata to avoid repeated calculations
|
||||
static ASSET_CACHE: Lazy<DashMap<String, AssetMetadata>> = Lazy::new(DashMap::new);
|
||||
static ASSET_CACHE: LazyLock<DashMap<String, AssetMetadata>> = LazyLock::new(DashMap::new);
|
||||
|
||||
/// Get cached asset metadata for a file path, caching on-demand
|
||||
/// Returns AssetMetadata containing MIME type and RapidHash hash
|
||||
|
||||
Reference in New Issue
Block a user