158 lines
4.5 KiB
Rust
158 lines
4.5 KiB
Rust
//! Custom chat draft use cases.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use domain::ports::{ChatDraftStore, Clock, ProjectStore};
|
|
use domain::{ChatDraft, ChatDraftKey, LayoutId, NodeId, Project, ProjectId};
|
|
|
|
use crate::AppError;
|
|
|
|
/// Input identifying one draft scope.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub struct ChatDraftScopeInput {
|
|
/// Project owning the layout.
|
|
pub project_id: ProjectId,
|
|
/// Named layout containing the chat cell.
|
|
pub layout_id: LayoutId,
|
|
/// Layout node hosting the chat cell.
|
|
pub node_id: NodeId,
|
|
}
|
|
|
|
impl ChatDraftScopeInput {
|
|
fn key(self) -> ChatDraftKey {
|
|
ChatDraftKey::new(self.project_id, self.layout_id, self.node_id)
|
|
}
|
|
}
|
|
|
|
/// Output of [`ReadChatDraft`].
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct ReadChatDraftOutput {
|
|
/// Restored draft text, or `None` when no draft exists.
|
|
pub text: Option<String>,
|
|
/// Last update time when a draft exists.
|
|
pub updated_at_ms: Option<u64>,
|
|
}
|
|
|
|
/// Input for [`SaveChatDraft`].
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct SaveChatDraftInput {
|
|
/// Draft scope.
|
|
pub scope: ChatDraftScopeInput,
|
|
/// Text to persist. Empty text clears the draft.
|
|
pub text: String,
|
|
}
|
|
|
|
/// Reads a persisted custom chat draft.
|
|
pub struct ReadChatDraft {
|
|
projects: Arc<dyn ProjectStore>,
|
|
drafts: Arc<dyn ChatDraftStore>,
|
|
}
|
|
|
|
impl ReadChatDraft {
|
|
/// Builds the use case.
|
|
#[must_use]
|
|
pub fn new(projects: Arc<dyn ProjectStore>, drafts: Arc<dyn ChatDraftStore>) -> Self {
|
|
Self { projects, drafts }
|
|
}
|
|
|
|
/// Executes the read.
|
|
pub async fn execute(
|
|
&self,
|
|
input: ChatDraftScopeInput,
|
|
) -> Result<ReadChatDraftOutput, AppError> {
|
|
let project = self.load_project(input.project_id).await?;
|
|
let draft = self.drafts.read(&project.root, input.key()).await?;
|
|
Ok(ReadChatDraftOutput {
|
|
text: draft.as_ref().map(|draft| draft.text.clone()),
|
|
updated_at_ms: draft.map(|draft| draft.updated_at_ms),
|
|
})
|
|
}
|
|
|
|
async fn load_project(&self, project_id: ProjectId) -> Result<Project, AppError> {
|
|
self.projects
|
|
.load_project(project_id)
|
|
.await
|
|
.map_err(AppError::from)
|
|
}
|
|
}
|
|
|
|
/// Saves a persisted custom chat draft.
|
|
pub struct SaveChatDraft {
|
|
projects: Arc<dyn ProjectStore>,
|
|
drafts: Arc<dyn ChatDraftStore>,
|
|
clock: Arc<dyn Clock>,
|
|
}
|
|
|
|
impl SaveChatDraft {
|
|
/// Builds the use case.
|
|
#[must_use]
|
|
pub fn new(
|
|
projects: Arc<dyn ProjectStore>,
|
|
drafts: Arc<dyn ChatDraftStore>,
|
|
clock: Arc<dyn Clock>,
|
|
) -> Self {
|
|
Self {
|
|
projects,
|
|
drafts,
|
|
clock,
|
|
}
|
|
}
|
|
|
|
/// Executes the save. Empty text clears the draft.
|
|
pub async fn execute(&self, input: SaveChatDraftInput) -> Result<(), AppError> {
|
|
let project = self.load_project(input.scope.project_id).await?;
|
|
let key = input.scope.key();
|
|
if input.text.is_empty() {
|
|
return self
|
|
.drafts
|
|
.clear(&project.root, key)
|
|
.await
|
|
.map_err(AppError::from);
|
|
}
|
|
let updated_at_ms = u64::try_from(self.clock.now_millis()).unwrap_or(0);
|
|
let draft = ChatDraft::new(key, input.text, updated_at_ms)
|
|
.map_err(|err| AppError::Invalid(err.to_string()))?;
|
|
self.drafts
|
|
.save(&project.root, draft)
|
|
.await
|
|
.map_err(AppError::from)
|
|
}
|
|
|
|
async fn load_project(&self, project_id: ProjectId) -> Result<Project, AppError> {
|
|
self.projects
|
|
.load_project(project_id)
|
|
.await
|
|
.map_err(AppError::from)
|
|
}
|
|
}
|
|
|
|
/// Clears a persisted custom chat draft.
|
|
pub struct ClearChatDraft {
|
|
projects: Arc<dyn ProjectStore>,
|
|
drafts: Arc<dyn ChatDraftStore>,
|
|
}
|
|
|
|
impl ClearChatDraft {
|
|
/// Builds the use case.
|
|
#[must_use]
|
|
pub fn new(projects: Arc<dyn ProjectStore>, drafts: Arc<dyn ChatDraftStore>) -> Self {
|
|
Self { projects, drafts }
|
|
}
|
|
|
|
/// Executes the clear.
|
|
pub async fn execute(&self, input: ChatDraftScopeInput) -> Result<(), AppError> {
|
|
let project = self.load_project(input.project_id).await?;
|
|
self.drafts
|
|
.clear(&project.root, input.key())
|
|
.await
|
|
.map_err(AppError::from)
|
|
}
|
|
|
|
async fn load_project(&self, project_id: ProjectId) -> Result<Project, AppError> {
|
|
self.projects
|
|
.load_project(project_id)
|
|
.await
|
|
.map_err(AppError::from)
|
|
}
|
|
}
|