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,106 @@
//! JSONL filebacked `EditLogRepository`.
//!
//! Path: `<session_dir>/edits.jsonl`
//!
//! Append-only log: new entries are appended to the file, never rewritten.
//! In-memory cache is capped at 10K entries to prevent unbounded growth.
#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::cast_possible_wrap
)]
use std::io::{BufRead, BufReader, Write};
use std::path::Path;
use anyhow::{Context, Result};
use crate::domain::edit_log::{EditLog, EditLogEntry, MAX_MEMORY_ENTRIES};
use crate::domain::repository::EditLogRepository;
/// Persists `EditLog` as an append-only JSONL file at `<session_dir>/edits.jsonl`.
#[derive(Debug, Clone, Default)]
pub struct JsonlEditLogRepository;
impl JsonlEditLogRepository {
/// Create a new repository instance.
pub fn new() -> Self {
Self
}
/// Read existing entries from disk into memory, capped at `MAX_MEMORY_ENTRIES`.
fn load_from_disk(path: &Path) -> Vec<EditLogEntry> {
let Ok(file) = std::fs::File::open(path) else {
return Vec::new();
};
let reader = BufReader::new(file);
let mut entries: Vec<EditLogEntry> = Vec::new();
for line in reader.lines() {
let Ok(line) = line else {
continue;
};
if let Ok(entry) = serde_json::from_str::<EditLogEntry>(&line) {
if entries.len() >= MAX_MEMORY_ENTRIES {
entries.remove(0);
}
entries.push(entry);
}
}
entries
}
}
impl EditLogRepository for JsonlEditLogRepository {
fn open(&self, session_dir: &Path) -> Result<EditLog> {
let path = session_dir.join("edits.jsonl");
// Ensure parent dir exists
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("failed to create session dir '{}'", parent.display()))?;
}
let entries = Self::load_from_disk(&path);
// Touch the file if it doesn't exist yet
if !path.exists() {
std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&path)
.with_context(|| format!("failed to create edits.jsonl at '{}'", path.display()))?;
}
Ok(EditLog { entries })
}
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";
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("failed to create session dir '{}'", parent.display()))?;
}
{
let mut file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&path)
.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")?;
}
log.entries.push(entry);
// Enforce in-memory cap
if log.entries.len() > MAX_MEMORY_ENTRIES {
log.entries.remove(0);
}
Ok(())
}
fn entries(&self, log: &EditLog) -> Vec<EditLogEntry> {
log.entries.clone()
}
}