refactor(domain): Row structs for from_persistence, ID newtypes, kill clippy.toml

- delete clippy.toml (too-many-arguments-threshold=20 hack)
- ChannelRow/MediaItemRow/LibraryItemRow structs for from_persistence
- SnapshotId/ActivityEventId/PlaybackRecordId newtypes
- DomainError variants use ChannelId/UserId instead of Uuid
- ActivityEvent.channel_id: Option<ChannelId> not Option<Uuid>
- InMemory repos key on newtype IDs
- AlgorithmicParams struct for schedule engine
- update all adapters/application/presentation callers
This commit is contained in:
2026-07-12 05:00:49 +02:00
parent 031cba5cfb
commit c0e685a4ee
65 changed files with 684 additions and 695 deletions

View File

@@ -6,7 +6,7 @@ use uuid::Uuid;
use adapter_common::{map_sqlx_error, parse_dt, parse_uuid};
use domain::{
ports::activity::{ActivityLogCommand, ActivityLogQuery},
ActivityEvent, ChannelId, DomainResult,
ActivityEvent, ActivityEventId, ChannelId, DomainResult,
};
pub struct SqliteActivityLog {
@@ -66,9 +66,11 @@ impl ActivityLogQuery for SqliteActivityLog {
let Ok(timestamp) = parse_dt(&ts_str) else {
continue;
};
let channel_id = channel_id_str.and_then(|s| Uuid::parse_str(&s).ok());
let channel_id = channel_id_str
.and_then(|s| Uuid::parse_str(&s).ok())
.map(ChannelId::from_uuid);
events.push(ActivityEvent::from_persistence(
id,
ActivityEventId::from_uuid(id),
timestamp,
event_type,
detail,

View File

@@ -9,8 +9,8 @@ use adapter_common::{
};
use domain::{
ports::channel::{ChannelCommand, ChannelQuery},
AccessMode, Channel, ChannelConfigSnapshot, ChannelId, DomainError, DomainResult, LogoPosition,
ScheduleConfig, UserId,
AccessMode, Channel, ChannelConfigSnapshot, ChannelId, ChannelRow as DomainChannelRow,
DomainError, DomainResult, LogoPosition, ScheduleConfig, SnapshotId, UserId,
};
pub struct SqliteChannelRepository {
@@ -57,27 +57,27 @@ impl ChannelRow {
let access_mode: AccessMode = parse_enum_or_default(self.access_mode);
let logo_position: LogoPosition = parse_enum_or_default(self.logo_position);
Ok(Channel::from_persistence(
Ok(Channel::from_persistence(DomainChannelRow {
id,
owner_id,
self.name,
self.description,
self.timezone,
name: self.name,
description: self.description,
timezone: self.timezone,
schedule_config,
recycle_policy,
self.auto_schedule != 0,
auto_schedule: self.auto_schedule != 0,
access_mode,
self.access_password_hash,
self.logo,
access_password_hash: self.access_password_hash,
logo: self.logo,
logo_position,
self.logo_opacity,
self.webhook_url,
self.webhook_poll_interval_secs as u32,
self.webhook_body_template,
self.webhook_headers,
parse_dt(&self.created_at)?,
parse_dt(&self.updated_at)?,
))
logo_opacity: self.logo_opacity,
webhook_url: self.webhook_url,
webhook_poll_interval_secs: self.webhook_poll_interval_secs as u32,
webhook_body_template: self.webhook_body_template,
webhook_headers: self.webhook_headers,
created_at: parse_dt(&self.created_at)?,
updated_at: parse_dt(&self.updated_at)?,
}))
}
}
@@ -86,7 +86,7 @@ fn map_snapshot_row(
channel_id: ChannelId,
) -> DomainResult<ChannelConfigSnapshot> {
let id_str: String = row.get("id");
let id = parse_uuid(&id_str, "snapshot id")?;
let id = SnapshotId::from_uuid(parse_uuid(&id_str, "snapshot id")?);
let config_json: String = row.get("config_json");
let config = parse_schedule_config(&config_json)?;
let version_num: i64 = row.get("version_num");
@@ -214,7 +214,7 @@ impl ChannelCommand for SqliteChannelRepository {
tx.commit().await.map_err(map_sqlx_error)?;
Ok(ChannelConfigSnapshot::from_persistence(
id,
SnapshotId::from_uuid(id),
channel_id,
config.clone(),
version_num,
@@ -226,14 +226,14 @@ impl ChannelCommand for SqliteChannelRepository {
async fn patch_config_snapshot_label(
&self,
channel_id: ChannelId,
snapshot_id: Uuid,
snapshot_id: SnapshotId,
label: Option<String>,
) -> DomainResult<Option<ChannelConfigSnapshot>> {
let updated = sqlx::query(
"UPDATE channel_config_snapshots SET label = ? WHERE id = ? AND channel_id = ? RETURNING id",
)
.bind(&label)
.bind(snapshot_id.to_string())
.bind(snapshot_id.value().to_string())
.bind(channel_id.value().to_string())
.fetch_optional(&self.pool)
.await
@@ -316,13 +316,13 @@ impl ChannelQuery for SqliteChannelRepository {
async fn get_config_snapshot(
&self,
channel_id: ChannelId,
snapshot_id: Uuid,
snapshot_id: SnapshotId,
) -> DomainResult<Option<ChannelConfigSnapshot>> {
let row = sqlx::query(
"SELECT id, config_json, version_num, label, created_at
FROM channel_config_snapshots WHERE id = ? AND channel_id = ?",
)
.bind(snapshot_id.to_string())
.bind(snapshot_id.value().to_string())
.bind(channel_id.value().to_string())
.fetch_optional(&self.pool)
.await

View File

@@ -4,8 +4,9 @@ use sqlx::SqlitePool;
use adapter_common::{content_type_str, parse_content_type, parse_genres_blob};
use domain::{
ports::library::{LibraryCommand, LibraryQuery},
ContentType, DomainError, DomainResult, LibraryCollection, LibraryItem, LibrarySearchFilter,
LibrarySyncLogEntry, LibrarySyncResult, SeasonSummary, ShowSummary,
ContentType, DomainError, DomainResult, LibraryCollection, LibraryItem,
LibraryItemRow as DomainLibraryItemRow, LibrarySearchFilter, LibrarySyncLogEntry,
LibrarySyncResult, SeasonSummary, ShowSummary,
};
pub struct SqliteLibraryRepository {
@@ -41,25 +42,25 @@ struct LibraryItemRow {
impl LibraryItemRow {
fn into_library_item(self) -> LibraryItem {
LibraryItem::from_persistence(
self.id,
self.provider_id,
self.external_id,
self.title,
parse_content_type(&self.content_type),
self.duration_secs as u32,
self.series_name,
self.season_number.map(|n| n as u32),
self.episode_number.map(|n| n as u32),
self.year.map(|n| n as u16),
serde_json::from_str(&self.genres).unwrap_or_default(),
serde_json::from_str(&self.tags).unwrap_or_default(),
self.collection_id,
self.collection_name,
self.collection_type,
self.thumbnail_url,
self.synced_at,
)
LibraryItem::from_persistence(DomainLibraryItemRow {
id: self.id,
provider_id: self.provider_id,
external_id: self.external_id,
title: self.title,
content_type: parse_content_type(&self.content_type),
duration_secs: self.duration_secs as u32,
series_name: self.series_name,
season_number: self.season_number.map(|n| n as u32),
episode_number: self.episode_number.map(|n| n as u32),
year: self.year.map(|n| n as u16),
genres: serde_json::from_str(&self.genres).unwrap_or_default(),
tags: serde_json::from_str(&self.tags).unwrap_or_default(),
collection_id: self.collection_id,
collection_name: self.collection_name,
collection_type: self.collection_type,
thumbnail_url: self.thumbnail_url,
synced_at: self.synced_at,
})
}
}

View File

@@ -7,7 +7,7 @@ use adapter_common::{map_sqlx_error, parse_dt, parse_json, parse_uuid};
use domain::{
ports::schedule::{ScheduleCommand, ScheduleQuery},
BlockId, ChannelId, DomainError, DomainResult, GeneratedSchedule, MediaItem, MediaItemId,
PlaybackRecord, ScheduleId, ScheduledSlot, SlotId,
PlaybackRecord, PlaybackRecordId, ScheduleId, ScheduledSlot, SlotId,
};
pub struct SqliteScheduleRepository {
@@ -84,7 +84,7 @@ fn map_schedule(row: ScheduleRow, slot_rows: Vec<SlotRow>) -> DomainResult<Gener
}
fn map_playback_row(row: PlaybackRecordRow) -> DomainResult<PlaybackRecord> {
let id = parse_uuid(&row.id, "playback record id")?;
let id = PlaybackRecordId::from_uuid(parse_uuid(&row.id, "playback record id")?);
let channel_id = ChannelId::from_uuid(parse_uuid(&row.channel_id, "channel id")?);
Ok(PlaybackRecord::from_persistence(