Refactor and clean up code across multiple modules
- Simplified token type assignment in OAuth service. - Removed unused session_lock module and re-exported Session from zesdex_entities. - Cleaned up session entity by removing unnecessary comments and code. - Consolidated session handling in HTTP handlers for better readability. - Improved formatting and readability in OAuth repository tests. - Enhanced session lock repository with clearer match statements. - Streamlined session repository error handling. - Refined RNG tests for better clarity. - Adjusted module visibility and organization in lib.rs. - Updated IPC client and connection code for better error handling and clarity. - Improved frame handling in IPC for better readability. - Organized module imports and added test utilities for IPC. - Enhanced database connection error handling. - Simplified JWT token creation error handling. - Improved password verification error handling. - Cleaned up state management code for better readability. - Refactored middleware for session authentication and rate limiting. - Simplified clipboard utility for better error handling. - Enhanced logging initialization for better error reporting. - Improved pagination utility with clearer method annotations. - Cleaned up sanitization functions for filenames and paths. - Enhanced slug generation functions for better clarity and usability.
This commit is contained in:
@@ -183,10 +183,7 @@ impl<R: OAuthRepository> OAuthService for OAuthServiceImpl<R> {
|
||||
access_token,
|
||||
refresh_token: body["refresh_token"].as_str().map(String::from),
|
||||
expires_at: now + expires_in,
|
||||
token_type: body["token_type"]
|
||||
.as_str()
|
||||
.unwrap_or("Bearer")
|
||||
.to_string(),
|
||||
token_type: body["token_type"].as_str().unwrap_or("Bearer").to_string(),
|
||||
};
|
||||
|
||||
self.token_repo.save_token(&self.token_path, &token)?;
|
||||
|
||||
@@ -2,4 +2,3 @@ pub mod oauth;
|
||||
pub mod repository;
|
||||
pub mod service;
|
||||
pub mod session;
|
||||
pub mod session_lock;
|
||||
|
||||
@@ -1,59 +1,5 @@
|
||||
#![allow(
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_sign_loss,
|
||||
clippy::cast_precision_loss,
|
||||
clippy::cast_possible_wrap
|
||||
)]
|
||||
//! Pure Session entity — no persistence logic.
|
||||
//! Pure Session entity
|
||||
//!
|
||||
//! This type represents the metadata of one conversation session.
|
||||
//! All save / load / list operations belong to the repository traits.
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
//! Re-exported from zesdex_entities for consistency.
|
||||
|
||||
/// Metadata for one conversation session.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Session {
|
||||
pub id: String,
|
||||
pub created_at: i64,
|
||||
pub updated_at: i64,
|
||||
pub title: String,
|
||||
pub model: String,
|
||||
pub workspace_roots: Vec<PathBuf>,
|
||||
pub message_count: u32,
|
||||
pub token_count: u32,
|
||||
pub archived: bool,
|
||||
pub summary: Option<String>,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
/// Create a new session with default field values.
|
||||
pub fn new(id: String, title: String) -> Self {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis() as i64;
|
||||
Session {
|
||||
id,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
title,
|
||||
model: "anthropic/claude-opus-4-8".to_string(),
|
||||
workspace_roots: vec![std::env::current_dir().unwrap_or_default()],
|
||||
message_count: 0,
|
||||
token_count: 0,
|
||||
archived: false,
|
||||
summary: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute this session's directory under `<base_dir>/sessions/<id>`.
|
||||
pub fn session_dir(&self, base_dir: &Path) -> PathBuf {
|
||||
base_dir.join("sessions").join(&self.id)
|
||||
}
|
||||
|
||||
/// Compute this session's conversation.json path.
|
||||
pub fn conversation_path(&self, base_dir: &Path) -> PathBuf {
|
||||
self.session_dir(base_dir).join("conversation.json")
|
||||
}
|
||||
}
|
||||
pub use zesdex_entities::seaorm::auth::session::Session;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
#![allow(
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_sign_loss,
|
||||
clippy::cast_precision_loss,
|
||||
clippy::cast_possible_wrap
|
||||
)]
|
||||
//! Pure SessionLock entity — no lock / unlock logic.
|
||||
//!
|
||||
//! Lock acquisition and release are handled by the repository.
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// A PID-file based session lock handle.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SessionLock {
|
||||
pub path: PathBuf,
|
||||
pub pid: u32,
|
||||
}
|
||||
|
||||
impl SessionLock {
|
||||
/// Construct a lock handle for a session directory (does not acquire
|
||||
/// the lock yet — use the repository's `try_lock`).
|
||||
pub fn new(session_dir: &Path) -> Self {
|
||||
SessionLock {
|
||||
path: session_dir.join(".lock"),
|
||||
pid: std::process::id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,19 +27,14 @@ pub fn handle_create_session<S: SessionService>(
|
||||
}
|
||||
|
||||
/// Handle a list-sessions request.
|
||||
pub fn handle_list_sessions<S: SessionService>(
|
||||
service: &S,
|
||||
) -> anyhow::Result<SessionListResponse> {
|
||||
pub fn handle_list_sessions<S: SessionService>(service: &S) -> anyhow::Result<SessionListResponse> {
|
||||
let sessions = service.list_all()?;
|
||||
let total = sessions.len();
|
||||
Ok(SessionListResponse { sessions, total })
|
||||
}
|
||||
|
||||
/// Handle an archive-session request.
|
||||
pub fn handle_archive_session<S: SessionService>(
|
||||
service: &S,
|
||||
id: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
pub fn handle_archive_session<S: SessionService>(service: &S, id: &str) -> anyhow::Result<()> {
|
||||
service.archive_session(id)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -63,9 +58,7 @@ pub fn handle_complete_oauth<O: OAuthService>(
|
||||
}
|
||||
|
||||
/// Handle a get-token request.
|
||||
pub fn handle_get_token<O: OAuthService>(
|
||||
service: &O,
|
||||
) -> anyhow::Result<OAuthTokenResponse> {
|
||||
pub fn handle_get_token<O: OAuthService>(service: &O) -> anyhow::Result<OAuthTokenResponse> {
|
||||
let token = service
|
||||
.get_token()?
|
||||
.ok_or_else(|| anyhow::anyhow!("no OAuth token stored"))?;
|
||||
|
||||
@@ -65,7 +65,8 @@ mod tests {
|
||||
#[cfg(unix)]
|
||||
fn save_token_sets_owner_only_permissions() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let dir = std::env::temp_dir().join(format!("zesdex-iam-perm-test-{}", uuid::Uuid::new_v4()));
|
||||
let dir =
|
||||
std::env::temp_dir().join(format!("zesdex-iam-perm-test-{}", uuid::Uuid::new_v4()));
|
||||
let path = dir.join("oauth_test.json");
|
||||
let repo = FileSystemOAuthRepository::new();
|
||||
let token = OAuthToken {
|
||||
@@ -74,10 +75,14 @@ mod tests {
|
||||
expires_at: 0,
|
||||
token_type: "Bearer".to_string(),
|
||||
};
|
||||
repo.save_token(&path, &token).expect("save_token should succeed");
|
||||
repo.save_token(&path, &token)
|
||||
.expect("save_token should succeed");
|
||||
|
||||
let mode = std::fs::metadata(&path).unwrap().permissions().mode() & 0o777;
|
||||
assert_eq!(mode, 0o600, "token file must be readable/writable by owner only, got {mode:o}");
|
||||
assert_eq!(
|
||||
mode, 0o600,
|
||||
"token file must be readable/writable by owner only, got {mode:o}"
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,11 @@ impl SessionLockRepository for FileSystemSessionLockRepository {
|
||||
let path = session_dir.join(".lock");
|
||||
let pid = std::process::id();
|
||||
|
||||
match fs::OpenOptions::new().create_new(true).write(true).open(&path) {
|
||||
match fs::OpenOptions::new()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.open(&path)
|
||||
{
|
||||
Ok(mut file) => {
|
||||
write!(file, "{pid}")?;
|
||||
file.sync_all()?;
|
||||
@@ -49,7 +53,11 @@ impl SessionLockRepository for FileSystemSessionLockRepository {
|
||||
|
||||
let tmp = path.with_extension("lock.tmp");
|
||||
{
|
||||
let mut tmp_file = fs::OpenOptions::new().create(true).truncate(true).write(true).open(&tmp)?;
|
||||
let mut tmp_file = fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.open(&tmp)?;
|
||||
write!(tmp_file, "{pid}")?;
|
||||
tmp_file.sync_all()?;
|
||||
}
|
||||
@@ -89,7 +97,8 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
fn tmp_dir() -> std::path::PathBuf {
|
||||
let dir = std::env::temp_dir().join(format!("zesdex-iam-lock-test-{}", uuid::Uuid::new_v4()));
|
||||
let dir =
|
||||
std::env::temp_dir().join(format!("zesdex-iam-lock-test-{}", uuid::Uuid::new_v4()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
dir
|
||||
}
|
||||
@@ -119,7 +128,10 @@ mod tests {
|
||||
let repo = FileSystemSessionLockRepository::new();
|
||||
// Write a lock file with a PID that cannot possibly be alive.
|
||||
std::fs::write(dir.join(".lock"), "999999999").unwrap();
|
||||
assert!(repo.try_lock(&dir).unwrap(), "a stale lock (dead PID) must be recoverable");
|
||||
assert!(
|
||||
repo.try_lock(&dir).unwrap(),
|
||||
"a stale lock (dead PID) must be recoverable"
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,9 +46,7 @@ impl SessionRepository for FileSystemSessionRepository {
|
||||
fn load_session(&self, base_dir: &Path, id: &str) -> anyhow::Result<Session> {
|
||||
// Directory-traversal prevention.
|
||||
if id.contains('/') || id.contains('\\') || id.contains("..") {
|
||||
anyhow::bail!(
|
||||
"invalid session id '{id}': must not contain path separators"
|
||||
);
|
||||
anyhow::bail!("invalid session id '{id}': must not contain path separators");
|
||||
}
|
||||
let path = base_dir.join("sessions").join(id).join("session.json");
|
||||
if !path.exists() {
|
||||
@@ -79,9 +77,7 @@ impl SessionRepository for FileSystemSessionRepository {
|
||||
|
||||
fn delete_session(&self, base_dir: &Path, id: &str) -> anyhow::Result<()> {
|
||||
if id.contains('/') || id.contains('\\') || id.contains("..") {
|
||||
anyhow::bail!(
|
||||
"invalid session id '{id}': must not contain path separators"
|
||||
);
|
||||
anyhow::bail!("invalid session id '{id}': must not contain path separators");
|
||||
}
|
||||
let dir = base_dir.join("sessions").join(id);
|
||||
if dir.exists() {
|
||||
|
||||
@@ -33,6 +33,9 @@ mod tests {
|
||||
fn secure_token_hex_is_not_constant() {
|
||||
let a = secure_token_hex(16);
|
||||
let b = secure_token_hex(16);
|
||||
assert_ne!(a, b, "two consecutive calls must not produce the same token");
|
||||
assert_ne!(
|
||||
a, b,
|
||||
"two consecutive calls must not produce the same token"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
//! - **application**— Use-case implementations of the service traits
|
||||
//! - **infrastructure** — Concrete persistence (filesystem) and HTTP adapter layers
|
||||
|
||||
pub mod domain;
|
||||
pub mod application;
|
||||
pub mod domain;
|
||||
pub mod infrastructure;
|
||||
|
||||
Reference in New Issue
Block a user