feat(domain): add StoredSong, SongSummary, SongRepositoryPort

This commit is contained in:
2026-04-08 03:03:58 +02:00
parent 852b1a8d0e
commit 8f9886c9a9
4 changed files with 56 additions and 1 deletions

View File

@@ -17,7 +17,7 @@ thiserror = "2.0.18"
tokio = { version = "1.51.0", features = ["full"] }
tracing = "0.1.44"
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
uuid = { version = "1.23.0", features = ["v4"] }
uuid = { version = "1.23.0", features = ["v4", "serde"] }
rand = "0.10.0"
sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls"] }
async-trait = "0.1.89"

View File

@@ -7,5 +7,7 @@ pub mod transposer;
pub use note::Note;
pub use chord::Chord;
pub use song::{ChordPosition, LyricLine, Section, SectionKind, SongMeta, Song};
pub use song::{song_preview_chords, StoredSong, SongSummary};
pub use ports::{FetchError, ParseError, TabFetcherPort, TabParserPort, TabSource};
pub use ports::{RepositoryError, SongRepositoryPort};
pub use transposer::{ChordTransposer, TransposeError};

View File

@@ -35,3 +35,22 @@ pub trait TabFetcherPort: Send + Sync {
pub trait TabParserPort: Send + Sync {
fn parse(&self, html: &str) -> Result<Song, ParseError>;
}
use uuid::Uuid;
use crate::song::{StoredSong, SongSummary};
#[derive(Debug, Error)]
pub enum RepositoryError {
#[error("Song not found")]
NotFound,
#[error("Database error: {0}")]
Internal(String),
}
#[async_trait]
pub trait SongRepositoryPort: Send + Sync {
async fn save(&self, song: &Song) -> Result<StoredSong, RepositoryError>;
async fn list(&self) -> Result<Vec<SongSummary>, RepositoryError>;
async fn get(&self, id: Uuid) -> Result<Option<Song>, RepositoryError>;
async fn delete(&self, id: Uuid) -> Result<(), RepositoryError>;
}

View File

@@ -60,6 +60,40 @@ pub struct Song {
pub sections: Vec<Section>,
}
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoredSong {
pub id: Uuid,
pub song: Song,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SongSummary {
pub id: Uuid,
pub meta: SongMeta,
pub preview_chords: Vec<String>,
}
pub fn song_preview_chords(song: &Song) -> Vec<String> {
let mut seen = std::collections::HashSet::new();
let mut result = Vec::new();
'outer: for section in &song.sections {
for line in &section.lines {
for cp in &line.chords {
let name = cp.chord.name(true);
if seen.insert(name.clone()) {
result.push(name);
if result.len() >= 5 {
break 'outer;
}
}
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;