- Introduced `RepositoryError` and `ServiceError` enums in both IAM and CMS domains for better error management. - Updated domain traits and services to return specific error types instead of `anyhow::Result`. - Enhanced session and OAuth repository implementations to handle errors more explicitly. - Refactored session service methods to return `Result<T, ServiceError>` for improved error handling. - Updated HTTP handlers to utilize the new error types. - Modified password hashing functions to run in a blocking context using `tokio::task::spawn_blocking`. - Added tests for new error handling mechanisms and async password functions.
105 lines
4.1 KiB
Rust
105 lines
4.1 KiB
Rust
//! Conversation use-case implementations for the CMS.
|
|
//!
|
|
//! `ConversationServiceImpl` implements `ConversationService` (defined in
|
|
//! `domain::service`) and is generic over `R: ConversationRepository`
|
|
//! (defined in `domain::repository`), delegating all persistence to that
|
|
//! adapter. The repository is injected at composition root.
|
|
//!
|
|
//! ## Flow
|
|
//! Each method computes the session directory from the session ID, then
|
|
//! delegates the actual I/O to the injected `repo`. Error context is
|
|
//! added at this layer to identify which session caused the failure.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use tracing;
|
|
|
|
use crate::domain::conversation::{ChatMessage, Conversation};
|
|
use crate::domain::error::ServiceError;
|
|
use crate::domain::repository::ConversationRepository;
|
|
use crate::domain::service::ConversationService;
|
|
|
|
/// Service implementation for conversation CRUD operations.
|
|
///
|
|
/// Generic over `R: ConversationRepository` so the persistence layer
|
|
/// can be swapped without changing business logic.
|
|
///
|
|
/// ## Fields
|
|
/// - `repo` — injected conversation repository implementation
|
|
/// - `sessions_dir` — base path under which session directories live
|
|
pub struct ConversationServiceImpl<R> {
|
|
pub repo: R,
|
|
/// Base directory containing session subdirectories.
|
|
pub sessions_dir: PathBuf,
|
|
}
|
|
|
|
impl<R: ConversationRepository> ConversationServiceImpl<R> {
|
|
/// Create a new service with the given repository and sessions directory.
|
|
///
|
|
/// ## Parameters
|
|
/// - `repo` — the repository adapter to delegate persistence to
|
|
/// - `sessions_dir` — base path for session directories (converted via `Into`)
|
|
pub fn new(repo: R, sessions_dir: impl Into<PathBuf>) -> Self {
|
|
tracing::debug!("creating ConversationServiceImpl");
|
|
Self {
|
|
repo,
|
|
sessions_dir: sessions_dir.into(),
|
|
}
|
|
}
|
|
|
|
/// Compute the session directory for a given session id.
|
|
///
|
|
/// Returns `{sessions_dir}/{session_id}`.
|
|
fn session_dir(&self, session_id: &str) -> PathBuf {
|
|
self.sessions_dir.join(session_id)
|
|
}
|
|
}
|
|
|
|
impl<R: ConversationRepository> ConversationService for ConversationServiceImpl<R> {
|
|
/// Load a conversation from disk for the given session.
|
|
///
|
|
/// Flow: resolve session dir → delegate to repo.load() → wrap error with context.
|
|
fn load_conversation(&self, session_id: &str) -> Result<Conversation, ServiceError> {
|
|
tracing::debug!("loading conversation for session {session_id}");
|
|
let dir = self.session_dir(session_id);
|
|
self.repo.load(&dir).map_err(|e| {
|
|
ServiceError::Other(format!(
|
|
"failed to load conversation for session '{session_id}': {e}"
|
|
))
|
|
})
|
|
}
|
|
|
|
/// Persist a conversation to disk.
|
|
///
|
|
/// Flow: resolve session dir from conv.session_id → delegate to repo.save() → wrap error.
|
|
fn save_conversation(&self, conv: &Conversation) -> Result<(), ServiceError> {
|
|
tracing::debug!("saving conversation for session {}", conv.session_id);
|
|
let dir = self.session_dir(&conv.session_id);
|
|
self.repo.save(&dir, conv).map_err(|e| {
|
|
ServiceError::Other(format!(
|
|
"failed to save conversation for session '{}': {e}",
|
|
conv.session_id
|
|
))
|
|
})
|
|
}
|
|
|
|
/// Add a message to a conversation and persist immediately.
|
|
///
|
|
/// Flow: push message to in-memory conversation → resolve session dir → delegate save.
|
|
///
|
|
/// ## Note
|
|
/// This is a write-through operation: the message is appended to the
|
|
/// in-memory `Conversation` and then the full conversation is persisted.
|
|
fn add_message(&self, conv: &mut Conversation, msg: ChatMessage) -> Result<(), ServiceError> {
|
|
tracing::debug!("adding message to session {}", conv.session_id);
|
|
conv.push(msg); // append message to in-memory conversation
|
|
let dir = self.session_dir(&conv.session_id);
|
|
self.repo.save(&dir, conv).map_err(|e| {
|
|
ServiceError::Other(format!(
|
|
"failed to persist conversation after adding message for session '{}': {e}",
|
|
conv.session_id
|
|
))
|
|
})
|
|
}
|
|
}
|