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:
asepharyana
2026-07-17 09:08:41 +07:00
parent 22dd6fdda7
commit 1f0ae9f551
95 changed files with 1792 additions and 2131 deletions
@@ -47,16 +47,22 @@ impl<R: ConversationRepository> ConversationService for ConversationServiceImpl<
fn save_conversation(&self, conv: &Conversation) -> Result<()> {
let dir = self.session_dir(&conv.session_id);
self.repo
.save(&dir, conv)
.with_context(|| format!("failed to save conversation for session '{}'", conv.session_id))
self.repo.save(&dir, conv).with_context(|| {
format!(
"failed to save conversation for session '{}'",
conv.session_id
)
})
}
fn add_message(&self, conv: &mut Conversation, msg: ChatMessage) -> Result<()> {
conv.push(msg);
let dir = self.session_dir(&conv.session_id);
self.repo
.save(&dir, conv)
.with_context(|| format!("failed to persist conversation after adding message for session '{}'", conv.session_id))
self.repo.save(&dir, conv).with_context(|| {
format!(
"failed to persist conversation after adding message for session '{}'",
conv.session_id
)
})
}
}
@@ -26,7 +26,11 @@ pub struct SettingsServiceImpl<S, C> {
impl<S: SettingsRepository, C: AppConfigRepository> SettingsServiceImpl<S, C> {
/// Create a new service with the given repositories and base directory.
pub fn new(settings_repo: S, app_config_repo: C, base_dir: impl Into<std::path::PathBuf>) -> Self {
pub fn new(
settings_repo: S,
app_config_repo: C,
base_dir: impl Into<std::path::PathBuf>,
) -> Self {
Self {
settings_repo,
app_config_repo,
@@ -46,7 +50,9 @@ impl<S: SettingsRepository, C: AppConfigRepository> SettingsService for Settings
fn update_provider(&self, name: &str, config: &ProviderConfig) -> Result<()> {
let mut app_config: AppConfig = self.app_config_repo.load(&self.base_dir)?;
app_config.providers.insert(name.to_string(), config.clone());
app_config
.providers
.insert(name.to_string(), config.clone());
self.app_config_repo.save(&self.base_dir, &app_config)
}
}
+3 -84
View File
@@ -1,87 +1,6 @@
//! Pure Conversation entity — in-memory message history plus system prompt
//! and LLM generation parameters.
//! Pure Conversation entity
//!
//! # Architecture
//! This is a pure data structure with **no I/O logic**. Load/save
//! responsibilities live in [`ConversationRepository`](super::repository::ConversationRepository).
#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::cast_possible_wrap
)]
use serde::{Deserialize, Serialize};
//! Re-exported from zesdex_entities for consistency.
pub use zesdex_entities::seaorm::common::message::{ChatMessage, Role};
/// A single conversation's message history and generation settings.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Conversation {
pub messages: Vec<ChatMessage>,
pub system_prompt: String,
pub session_id: String,
pub model: String,
pub max_tokens: Option<u32>,
pub temperature: Option<f32>,
}
impl Conversation {
/// Create an empty conversation with the given system prompt and
/// session id, using default model / token / temperature settings.
pub fn new(system_prompt: String, session_id: String) -> Self {
Self {
messages: Vec::new(),
system_prompt,
session_id,
model: "anthropic/claude-opus-4-8".to_string(),
max_tokens: None,
temperature: None,
}
}
/// Append a message to the conversation history.
pub fn push(&mut self, msg: ChatMessage) {
self.messages.push(msg);
}
/// Replace the system prompt and strip any prior `System`-role messages
/// from history.
pub fn rebuild_system(&mut self, new_prompt: String) {
self.system_prompt = new_prompt;
self.messages.retain(|m| !matches!(m.role, Role::System));
}
/// Build the message list to send to the LLM API, with the system
/// prompt prepended as the first message.
pub fn to_api_messages(&self) -> Vec<ChatMessage> {
let mut msgs = Vec::with_capacity(self.messages.len() + 1);
msgs.push(ChatMessage::system(&self.system_prompt));
msgs.extend(self.messages.iter().cloned());
msgs
}
/// Number of messages in the conversation history (excluding the
/// synthesized system message).
pub fn len(&self) -> usize {
self.messages.len()
}
/// Returns `true` if the conversation has no messages.
pub fn is_empty(&self) -> bool {
self.messages.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chat_message_is_the_canonical_entities_type() {
let canonical = zesdex_entities::seaorm::common::message::ChatMessage::user("hi");
let via_cms: ChatMessage = canonical;
assert_eq!(via_cms.content.as_deref(), Some("hi"));
}
}
pub use zesdex_entities::seaorm::common::conversation::Conversation;
+7 -1
View File
@@ -67,7 +67,13 @@ pub trait MemoryRepository {
/// caller-supplied key (e.g. a tool-call id) within a session.
pub trait RewindBlobRepository {
/// Store (or overwrite) a blob under `blob_key` for this session.
fn store_blob(&self, session_dir: &Path, blob_key: &str, data: &[u8], mime_type: Option<&str>) -> anyhow::Result<()>;
fn store_blob(
&self,
session_dir: &Path,
blob_key: &str,
data: &[u8],
mime_type: Option<&str>,
) -> anyhow::Result<()>;
/// Retrieve a blob's bytes by key, or `None` if not found.
fn retrieve_blob(&self, session_dir: &Path, blob_key: &str) -> anyhow::Result<Option<Vec<u8>>>;
+2 -1
View File
@@ -25,7 +25,8 @@ pub trait SettingsService {
fn save_settings(&self, settings: &Settings) -> Result<()>;
/// Update the provider configuration (name and details).
fn update_provider(&self, name: &str, config: &super::app_config::ProviderConfig) -> Result<()>;
fn update_provider(&self, name: &str, config: &super::app_config::ProviderConfig)
-> Result<()>;
}
/// Conversation management use cases.
@@ -18,17 +18,13 @@ use crate::domain::memory::Memory;
use crate::domain::service::{MemoryService, SettingsService};
use crate::domain::settings::Settings;
use super::dto::{
MemoryCreateRequest, MemoryResponse, SettingsResponse, SettingsUpdateRequest,
};
use super::dto::{MemoryCreateRequest, MemoryResponse, SettingsResponse, SettingsUpdateRequest};
/// Handle `GET /settings`
///
/// Returns the current settings as a `SettingsResponse`.
pub fn handle_get_settings<S: SettingsService>(service: &S) -> Result<SettingsResponse> {
let settings = service
.load_settings()
.context("failed to load settings")?;
let settings = service.load_settings().context("failed to load settings")?;
Ok(SettingsResponse::from(settings))
}
@@ -115,9 +111,7 @@ pub fn handle_update_settings<S: SettingsService>(
///
/// Lists all memory slugs, then loads each memory to return full responses.
pub fn handle_list_memories<M: MemoryService>(service: &M) -> Result<Vec<MemoryResponse>> {
let slugs = service
.list_memories()
.context("failed to list memories")?;
let slugs = service.list_memories().context("failed to list memories")?;
// We can't load individual memories without a load_memory method on the
// service. For now, list returns summary info; callers who need full
@@ -135,8 +135,8 @@ impl AppConfigRepository for JsonAppConfigRepository {
.with_context(|| format!("failed to create base dir '{}'", base_dir.display()))?;
let path = base_dir.join("app_config.json");
let tmp = base_dir.join("app_config.json.tmp");
let json = serde_json::to_string_pretty(config)
.context("failed to serialize app config")?;
let json =
serde_json::to_string_pretty(config).context("failed to serialize app config")?;
{
let mut f = std::fs::OpenOptions::new()
.create(true)
@@ -147,8 +147,13 @@ impl AppConfigRepository for JsonAppConfigRepository {
f.write_all(json.as_bytes())?;
f.sync_all()?;
}
std::fs::rename(&tmp, &path)
.with_context(|| format!("failed to rename '{}' -> '{}'", tmp.display(), path.display()))?;
std::fs::rename(&tmp, &path).with_context(|| {
format!(
"failed to rename '{}' -> '{}'",
tmp.display(),
path.display()
)
})?;
if let Some(parent) = path.parent() {
if let Ok(d) = std::fs::File::open(parent) {
let _ = d.sync_all();
@@ -57,8 +57,13 @@ impl ConversationRepository for JsonConversationRepository {
f.write_all(json.as_bytes())?;
f.sync_all()?;
}
std::fs::rename(&tmp, &path)
.with_context(|| format!("failed to rename '{}' -> '{}'", tmp.display(), path.display()))?;
std::fs::rename(&tmp, &path).with_context(|| {
format!(
"failed to rename '{}' -> '{}'",
tmp.display(),
path.display()
)
})?;
if let Some(parent) = path.parent() {
if let Ok(d) = std::fs::File::open(parent) {
let _ = d.sync_all();
@@ -74,9 +74,8 @@ impl EditLogRepository for JsonlEditLogRepository {
fn append(&self, session_dir: &Path, log: &mut EditLog, entry: EditLogEntry) -> Result<()> {
let path = session_dir.join("edits.jsonl");
let line = serde_json::to_string(&entry)
.context("failed to serialize edit log entry")?
+ "\n";
let line =
serde_json::to_string(&entry).context("failed to serialize edit log entry")? + "\n";
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("failed to create session dir '{}'", parent.display()))?;
@@ -89,8 +88,7 @@ impl EditLogRepository for JsonlEditLogRepository {
.with_context(|| format!("failed to open edits.jsonl at '{}'", path.display()))?;
file.write_all(line.as_bytes())
.context("failed to write edit log entry")?;
file.sync_all()
.context("failed to fsync edit log")?;
file.sync_all().context("failed to fsync edit log")?;
}
log.entries.push(entry);
// Enforce in-memory cap
@@ -84,10 +84,7 @@ impl MarkdownMemoryRepository {
.lines()
.filter_map(|l| {
let mut it = l.splitn(2, ':');
Some((
it.next()?.trim().to_string(),
it.next()?.trim().to_string(),
))
Some((it.next()?.trim().to_string(), it.next()?.trim().to_string()))
})
.collect()
}
@@ -155,7 +152,8 @@ impl MemoryRepository for MarkdownMemoryRepository {
if name == "MEMORY.md" {
return None;
}
name.strip_suffix(".md").map(std::string::ToString::to_string)
name.strip_suffix(".md")
.map(std::string::ToString::to_string)
})
.collect();
Ok(slugs)
@@ -190,8 +188,13 @@ impl MemoryRepository for MarkdownMemoryRepository {
f.write_all(content.as_bytes())?;
f.sync_all()?;
}
std::fs::rename(&tmp, &path)
.with_context(|| format!("failed to rename '{}' -> '{}'", tmp.display(), path.display()))?;
std::fs::rename(&tmp, &path).with_context(|| {
format!(
"failed to rename '{}' -> '{}'",
tmp.display(),
path.display()
)
})?;
if let Some(p) = path.parent() {
if let Ok(d) = std::fs::File::open(p) {
let _ = d.sync_all();
@@ -204,11 +207,15 @@ impl MemoryRepository for MarkdownMemoryRepository {
fn delete(&self, memory_dir: &Path, name: &str) -> Result<()> {
let path = Memory::path(memory_dir, name);
if path.exists() {
std::fs::remove_file(&path)
.with_context(|| format!("failed to delete memory '{name}' at '{}'", path.display()))?;
std::fs::remove_file(&path).with_context(|| {
format!("failed to delete memory '{name}' at '{}'", path.display())
})?;
tracing::debug!("memory deleted: '{}'", path.display());
} else {
tracing::warn!("memory '{name}' not found at '{}', skipping delete", path.display());
tracing::warn!(
"memory '{name}' not found at '{}', skipping delete",
path.display()
);
}
Ok(())
}
@@ -128,10 +128,8 @@ mod tests {
use super::*;
fn tmp_dir() -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!(
"zesdex-cms-blob-test-{}",
uuid::Uuid::new_v4()
));
let dir =
std::env::temp_dir().join(format!("zesdex-cms-blob-test-{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&dir).unwrap();
dir
}
@@ -179,10 +177,7 @@ mod tests {
vec!["k".to_string()],
"key must appear exactly once even after being overwritten"
);
assert_eq!(
repo.retrieve_blob(&dir, "k").unwrap(),
Some(b"v2".to_vec())
);
assert_eq!(repo.retrieve_blob(&dir, "k").unwrap(), Some(b"v2".to_vec()));
let _ = std::fs::remove_dir_all(&dir);
}
}
@@ -57,8 +57,8 @@ impl SettingsRepository for JsonSettingsRepository {
.with_context(|| format!("failed to create base dir '{}'", base_dir.display()))?;
let path = base_dir.join("settings.json");
let tmp = base_dir.join("settings.json.tmp");
let json = serde_json::to_string_pretty(settings)
.context("failed to serialize settings")?;
let json =
serde_json::to_string_pretty(settings).context("failed to serialize settings")?;
{
let mut f = std::fs::OpenOptions::new()
.create(true)
@@ -69,8 +69,13 @@ impl SettingsRepository for JsonSettingsRepository {
f.write_all(json.as_bytes())?;
f.sync_all()?;
}
std::fs::rename(&tmp, &path)
.with_context(|| format!("failed to rename '{}' -> '{}'", tmp.display(), path.display()))?;
std::fs::rename(&tmp, &path).with_context(|| {
format!(
"failed to rename '{}' -> '{}'",
tmp.display(),
path.display()
)
})?;
if let Some(parent) = path.parent() {
if let Ok(d) = std::fs::File::open(parent) {
let _ = d.sync_all();
@@ -87,7 +92,8 @@ mod tests {
#[test]
fn load_defaults_hive_mind_timeout_when_field_missing_from_old_settings_json() {
let dir = std::env::temp_dir().join(format!("zesdex-cms-settings-test-{}", uuid::Uuid::new_v4()));
let dir =
std::env::temp_dir().join(format!("zesdex-cms-settings-test-{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&dir).unwrap();
// Simulate a settings.json written before `hive_mind_node_timeout_ms` existed.
std::fs::write(
@@ -96,7 +102,9 @@ mod tests {
).unwrap();
let repo = JsonSettingsRepository::new();
let settings = repo.load(&dir).expect("load must not fail on a pre-existing settings.json missing the new field");
let settings = repo
.load(&dir)
.expect("load must not fail on a pre-existing settings.json missing the new field");
assert_eq!(settings.hive_mind_node_timeout_ms, 600_000);
let _ = std::fs::remove_dir_all(&dir);
+1 -1
View File
@@ -12,6 +12,6 @@
clippy::cast_possible_wrap
)]
pub mod domain;
pub mod application;
pub mod domain;
pub mod infrastructure;