refactor: migrate monolithic crate to Cargo Workspace with Clean Architecture

Transform the single binary crate into a 9-crate workspace monorepo:

- Root Cargo.toml as [workspace] manager with resolver = "2"
- zesdex-entities: Domain entity types (session, settings, store, message, etc.)
- zesdex-utils: Pure utility functions (error, logger, pagination, slug, clipboard)
- zesdex-dto: Data Transfer Objects for LLM provider API communication
- zesdex-ipc: Unix-socket IPC layer (client/server/framing/protocol)
- zesdex-iam: Identity & Access Management (Clean Architecture: domain/application/infrastructure)
- zesdex-cms: Content Management (Clean Architecture: domain/application/infrastructure)
- zesdex-middleware: HTTP middleware (Auth, CORS, Rate Limiting)
- zesdex-libs: Composition root (AppContext, DB init, JWT, Argon2)
- zesdex-backend: Main binary entry point + seed/migrate binaries
- DevOps: Dockerfile, docker-compose, Nix (flake/shell/default), CI/CD updates
- Remove dead root src/ and src-misc/ directories

All crate re-exports maintain backward compatibility with original
crate::model::*, crate::dto::*, crate::ipc::* module paths.
Feature crates enforce strict layer separation: domain -> application
-> infrastructure with generic trait-based dependency injection.
This commit is contained in:
asepharyana
2026-07-17 09:08:41 +07:00
parent 86cc412395
commit be0a9582bb
248 changed files with 7901 additions and 1505 deletions
@@ -0,0 +1,168 @@
//! CMS-specific DTOs (Data Transfer Objects) for the REST API.
//!
//! These types define the wire format accepted and returned by HTTP handlers.
//! They are independent of the domain entities so the API contract can
//! evolve without coupling to the domain model.
#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::cast_possible_wrap
)]
use serde::{Deserialize, Serialize};
// ---------------------------------------------------------------------------
// Settings
// ---------------------------------------------------------------------------
/// Request body for updating settings (partial update — only specified fields
/// are changed).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SettingsUpdateRequest {
pub internet_mode: Option<String>,
pub provider: Option<String>,
pub model: Option<String>,
pub api_keys: Option<std::collections::HashMap<String, String>>,
pub max_tokens: Option<Option<u32>>,
pub temperature: Option<Option<f32>>,
pub review_max_lessons_per_run: Option<usize>,
pub adaptive_review_max_skip: Option<u32>,
pub verify_command: Option<Option<String>>,
pub verify_timeout_ms: Option<u64>,
pub workflow_max_concurrency: Option<usize>,
pub review_enabled: Option<bool>,
pub session_archive_enabled: Option<bool>,
pub lsp_auto_provision: Option<bool>,
pub lsp_languages: Option<Vec<String>>,
pub hive_mind_node_timeout_ms: Option<u64>,
}
/// Response body for settings.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SettingsResponse {
pub internet_mode: String,
pub provider: String,
pub model: String,
pub api_keys: Vec<String>, // key names only, values redacted
pub max_tokens: Option<u32>,
pub temperature: Option<f32>,
pub review_max_lessons_per_run: usize,
pub adaptive_review_max_skip: u32,
pub verify_command: Option<String>,
pub verify_timeout_ms: u64,
pub workflow_max_concurrency: usize,
pub review_enabled: bool,
pub session_archive_enabled: bool,
pub lsp_auto_provision: bool,
pub lsp_languages: Vec<String>,
pub hive_mind_node_timeout_ms: u64,
}
impl From<crate::domain::settings::Settings> for SettingsResponse {
fn from(s: crate::domain::settings::Settings) -> Self {
Self {
internet_mode: format!("{:?}", s.internet_mode),
provider: s.provider,
model: s.model,
api_keys: s.api_keys.keys().cloned().collect(),
max_tokens: s.max_tokens,
temperature: s.temperature,
review_max_lessons_per_run: s.review_max_lessons_per_run,
adaptive_review_max_skip: s.adaptive_review_max_skip,
verify_command: s.verify_command,
verify_timeout_ms: s.verify_timeout_ms,
workflow_max_concurrency: s.workflow_max_concurrency,
review_enabled: s.flags.review_enabled,
session_archive_enabled: s.flags.session_archive_enabled,
lsp_auto_provision: s.flags.lsp_auto_provision,
lsp_languages: s.lsp_languages,
hive_mind_node_timeout_ms: s.hive_mind_node_timeout_ms,
}
}
}
// ---------------------------------------------------------------------------
// Memory
// ---------------------------------------------------------------------------
/// Request body for creating or updating a memory.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryCreateRequest {
pub name: String,
pub description: String,
pub content: String,
pub kind: Option<String>,
pub outcome: Option<String>,
pub lifecycle: Option<String>,
pub scope: Option<String>,
pub before_snippet: Option<String>,
pub after_snippet: Option<String>,
pub provenances: Option<Vec<String>>,
}
/// Response body for a memory.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryResponse {
pub name: String,
pub description: String,
pub content: String,
pub kind: String,
pub created_at: i64,
pub updated_at: i64,
pub outcome: Option<String>,
pub lifecycle: String,
pub scope: Option<String>,
pub before_snippet: Option<String>,
pub after_snippet: Option<String>,
pub provenances: Vec<String>,
}
impl From<crate::domain::memory::Memory> for MemoryResponse {
fn from(m: crate::domain::memory::Memory) -> Self {
Self {
name: m.name,
description: m.description,
content: m.content,
kind: m.kind,
created_at: m.created_at,
updated_at: m.updated_at,
outcome: m.outcome,
lifecycle: m.lifecycle,
scope: m.scope,
before_snippet: m.before_snippet,
after_snippet: m.after_snippet,
provenances: m.provenances,
}
}
}
// ---------------------------------------------------------------------------
// Conversation
// ---------------------------------------------------------------------------
/// Response body for a conversation.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConversationResponse {
pub session_id: String,
pub message_count: usize,
pub model: String,
pub system_prompt: String,
pub max_tokens: Option<u32>,
pub temperature: Option<f32>,
}
impl From<crate::domain::conversation::Conversation> for ConversationResponse {
fn from(c: crate::domain::conversation::Conversation) -> Self {
let message_count = c.len();
Self {
session_id: c.session_id,
message_count,
model: c.model,
system_prompt: c.system_prompt,
max_tokens: c.max_tokens,
temperature: c.temperature,
}
}
}