refactor: DDD/CQRS architecture, unified crate layout

- crates: common→application, api→presentation, infrastructure/*→adapters/*
- new crates: api-types, infra-wiring
- domain: errors/, models/, value_objects/, ports/, services/
- application: CQRS use cases (songs/, tabs/) w/ commands, queries, deps
- unified DomainError replaces RepositoryError
- workspace deps, unused dep cleanup
- fix: parse plain-text chord lines (UG drops spans mid-song)
- tests extracted to separate modules (tests/ dirs)
This commit is contained in:
2026-07-11 21:02:10 +02:00
parent a520251dab
commit d13df586dd
74 changed files with 1493 additions and 1076 deletions

View File

@@ -0,0 +1,4 @@
pub struct ParseTabCommand {
pub source: Option<String>,
pub html: Option<String>,
}

View File

@@ -0,0 +1,7 @@
use domain::ports::{TabFetcherPort, TabParserPort};
use std::sync::Arc;
pub struct ParseTabDeps {
pub fetcher: Arc<dyn TabFetcherPort>,
pub parser: Arc<dyn TabParserPort>,
}

View File

@@ -0,0 +1,3 @@
pub mod commands;
pub mod deps;
pub mod parse_tab;

View File

@@ -0,0 +1,33 @@
use std::path::PathBuf;
use domain::errors::DomainError;
use domain::models::Song;
use domain::ports::TabSource;
use super::commands::ParseTabCommand;
use super::deps::ParseTabDeps;
pub async fn execute(deps: &ParseTabDeps, cmd: ParseTabCommand) -> Result<Song, DomainError> {
let html = if let Some(raw_html) = cmd.html {
Ok(raw_html)
} else if let Some(source) = cmd.source {
let tab_source = if source.starts_with("file://") {
let path = source.trim_start_matches("file://");
TabSource::File(PathBuf::from(path))
} else {
TabSource::Url(source)
};
deps.fetcher
.fetch(tab_source)
.await
.map_err(|e| DomainError::InfrastructureError(e.to_string()))
} else {
Err(DomainError::ValidationError(
"Provide either 'source' or 'html'".into(),
))
}?;
deps.parser
.parse(&html)
.map_err(|e| DomainError::InfrastructureError(e.to_string()))
}