refactor(application): strip comments, DRY ownership check + parse_content_type

This commit is contained in:
2026-07-12 04:10:58 +02:00
parent 98a54245b1
commit 9dcd169689
62 changed files with 56 additions and 203 deletions

View File

@@ -4,7 +4,6 @@ use domain::DomainResult;
use super::deps::AdminDeps;
use super::queries::GetActivityLogQuery;
/// Get recent activity log entries.
pub async fn execute(
deps: &AdminDeps,
query: GetActivityLogQuery,

View File

@@ -1,4 +1,3 @@
/// Update one or more admin settings (key-value pairs).
pub struct UpdateSettingsCommand {
pub settings: Vec<(String, String)>,
}

View File

@@ -2,7 +2,6 @@ use std::sync::Arc;
use domain::ports::{ActivityLogQuery, AppSettingsRepository};
/// Dependencies for admin use cases.
pub struct AdminDeps {
pub settings_repo: Arc<dyn AppSettingsRepository>,
pub activity_query: Arc<dyn ActivityLogQuery>,

View File

@@ -3,7 +3,6 @@ use domain::DomainResult;
use super::deps::AdminDeps;
use super::queries::GetSettingsQuery;
/// Get all admin settings as key-value pairs.
pub async fn execute(
deps: &AdminDeps,
_query: GetSettingsQuery,

View File

@@ -1,7 +1,5 @@
/// Get all admin settings.
pub struct GetSettingsQuery;
/// Get recent activity log entries.
pub struct GetActivityLogQuery {
pub limit: u32,
}

View File

@@ -3,9 +3,6 @@ use domain::DomainResult;
use super::commands::UpdateSettingsCommand;
use super::deps::AdminDeps;
/// Update one or more admin settings.
///
/// Iterates key/value pairs and upserts each one.
pub async fn execute(deps: &AdminDeps, cmd: UpdateSettingsCommand) -> DomainResult<()> {
for (key, value) in &cmd.settings {
deps.settings_repo.set(key, value).await?;

View File

@@ -1,10 +1,8 @@
/// Register a new local user.
pub struct RegisterCommand {
pub email: String,
pub password: String,
}
/// Log in with email + password.
pub struct LoginCommand {
pub email: String,
pub password: String,

View File

@@ -2,10 +2,6 @@ use std::sync::Arc;
use domain::ports::{AuthService, EventPublisher, UserCommand, UserQuery};
/// Dependencies for auth use cases.
///
/// Aggregates the ports required by register/login operations.
/// Built once at startup and shared via `Arc<AuthDeps>` or passed by reference.
pub struct AuthDeps {
pub user_command: Arc<dyn UserCommand>,
pub user_query: Arc<dyn UserQuery>,

View File

@@ -4,30 +4,24 @@ use domain::{DomainError, DomainResult, Email};
use super::commands::LoginCommand;
use super::deps::AuthDeps;
/// Log in with email + password.
///
/// Flow: validate email -> find user -> verify password -> return User.
/// JWT generation belongs in the presentation layer, not here.
const INVALID_CREDENTIALS: &str = "Invalid credentials";
pub async fn execute(deps: &AuthDeps, cmd: LoginCommand) -> DomainResult<User> {
// Validate email format
let email = Email::new(&cmd.email)?;
// Find user
let user = deps
.user_query
.find_by_email(email.as_ref())
.await?
.ok_or_else(|| DomainError::unauthenticated("Invalid credentials"))?;
.ok_or_else(|| DomainError::unauthenticated(INVALID_CREDENTIALS))?;
// Must have a password hash (not an OIDC-only user)
let hash = user
.password_hash()
.ok_or_else(|| DomainError::unauthenticated("Invalid credentials"))?;
.ok_or_else(|| DomainError::unauthenticated(INVALID_CREDENTIALS))?;
// Verify password
let valid = deps.auth_service.verify_password(&cmd.password, hash)?;
if !valid {
return Err(DomainError::unauthenticated("Invalid credentials"));
return Err(DomainError::unauthenticated(INVALID_CREDENTIALS));
}
Ok(user)

View File

@@ -1 +0,0 @@
// Auth queries (reserved for future use, e.g. GetCurrentUserQuery).

View File

@@ -5,33 +5,23 @@ use domain::{DomainResult, Email, Password};
use super::commands::RegisterCommand;
use super::deps::AuthDeps;
/// Register a new local user.
///
/// Flow: validate email/password -> check duplicate -> hash password ->
/// create User (first user gets admin) -> save -> publish event -> return User.
pub async fn execute(deps: &AuthDeps, cmd: RegisterCommand) -> DomainResult<User> {
// Validate inputs via domain value objects
let email = Email::new(&cmd.email)?;
let password = Password::new(&cmd.password)?;
// Check for duplicate
if deps.user_query.find_by_email(email.as_ref()).await?.is_some() {
return Err(domain::DomainError::UserAlreadyExists(cmd.email));
}
// Hash password
let hash = deps.auth_service.hash_password(password.as_ref())?;
// Create user; first user gets admin
let mut user = User::new_local(email, hash);
if deps.user_query.count_users().await? == 0 {
user.promote_to_admin();
}
// Persist
deps.user_command.save(&user).await?;
// Publish event
deps.event_publisher
.publish(DomainEvent::UserRegistered {
user_id: user.id(),

View File

@@ -3,20 +3,16 @@ use uuid::Uuid;
use domain::models::ScheduleConfig;
use domain::value_objects::RecyclePolicy;
/// Create a new channel.
pub struct CreateChannelCommand {
pub owner_id: Uuid,
pub name: String,
pub timezone: String,
}
/// Update an existing channel (partial — only `Some` fields are applied).
pub struct UpdateChannelCommand {
pub channel_id: Uuid,
/// Used for ownership check.
pub owner_id: Uuid,
pub name: Option<String>,
/// `Some(None)` clears the description; `None` leaves it unchanged.
pub description: Option<Option<String>>,
pub timezone: Option<String>,
pub schedule_config: Option<ScheduleConfig>,
@@ -24,9 +20,7 @@ pub struct UpdateChannelCommand {
pub auto_schedule: Option<bool>,
}
/// Delete a channel.
pub struct DeleteChannelCommand {
pub channel_id: Uuid,
/// Used for ownership check.
pub owner_id: Uuid,
}

View File

@@ -6,9 +6,6 @@ use domain::DomainResult;
use super::commands::CreateChannelCommand;
use super::deps::ChannelCommandDeps;
/// Create a new channel.
///
/// Flow: convert raw IDs -> build Channel -> save -> publish event -> return.
pub async fn execute(deps: &ChannelCommandDeps, cmd: CreateChannelCommand) -> DomainResult<Channel> {
let owner_id = UserId::from(cmd.owner_id);
let channel = Channel::new(owner_id, cmd.name, cmd.timezone);

View File

@@ -1,26 +1,16 @@
use domain::events::DomainEvent;
use domain::value_objects::{ChannelId, UserId};
use domain::{DomainError, DomainResult};
use domain::DomainResult;
use super::commands::DeleteChannelCommand;
use super::deps::ChannelCommandDeps;
use super::find_owned_channel;
/// Delete a channel after verifying ownership.
///
/// Flow: find channel -> verify ownership -> delete -> publish event.
pub async fn execute(deps: &ChannelCommandDeps, cmd: DeleteChannelCommand) -> DomainResult<()> {
let channel_id = ChannelId::from(cmd.channel_id);
let owner_id = UserId::from(cmd.owner_id);
let channel = deps
.channel_query
.find_by_id(channel_id)
.await?
.ok_or(DomainError::ChannelNotFound(cmd.channel_id))?;
if channel.owner_id() != owner_id {
return Err(DomainError::forbidden("You don't own this channel"));
}
find_owned_channel(deps.channel_query.as_ref(), channel_id, owner_id, cmd.channel_id).await?;
deps.channel_command.delete(channel_id).await?;

View File

@@ -2,14 +2,12 @@ use std::sync::Arc;
use domain::ports::{ChannelCommand, ChannelQuery, EventPublisher};
/// Dependencies for channel write use cases (create, update, delete).
pub struct ChannelCommandDeps {
pub channel_command: Arc<dyn ChannelCommand>,
pub channel_query: Arc<dyn ChannelQuery>,
pub event_publisher: Arc<dyn EventPublisher>,
}
/// Dependencies for channel read use cases (get, list, list_by_owner).
pub struct ChannelQueryDeps {
pub channel_query: Arc<dyn ChannelQuery>,
}

View File

@@ -5,7 +5,6 @@ use domain::DomainResult;
use super::deps::ChannelQueryDeps;
use super::queries::GetChannelQuery;
/// Get a single channel by ID.
pub async fn execute(deps: &ChannelQueryDeps, query: GetChannelQuery) -> DomainResult<Option<Channel>> {
let channel_id = ChannelId::from(query.channel_id);
deps.channel_query.find_by_id(channel_id).await

View File

@@ -4,7 +4,6 @@ use domain::DomainResult;
use super::deps::ChannelQueryDeps;
use super::queries::ListChannelsQuery;
/// List all channels.
pub async fn execute(deps: &ChannelQueryDeps, _query: ListChannelsQuery) -> DomainResult<Vec<Channel>> {
deps.channel_query.find_all().await
}

View File

@@ -5,7 +5,6 @@ use domain::DomainResult;
use super::deps::ChannelQueryDeps;
use super::queries::ListByOwnerQuery;
/// List channels belonging to a specific owner.
pub async fn execute(deps: &ChannelQueryDeps, query: ListByOwnerQuery) -> DomainResult<Vec<Channel>> {
let owner_id = UserId::from(query.owner_id);
deps.channel_query.find_by_owner(owner_id).await

View File

@@ -11,3 +11,27 @@ pub mod update;
pub use commands::{CreateChannelCommand, DeleteChannelCommand, UpdateChannelCommand};
pub use deps::{ChannelCommandDeps, ChannelQueryDeps};
pub use queries::{GetChannelQuery, ListByOwnerQuery, ListChannelsQuery};
use domain::models::Channel;
use domain::value_objects::{ChannelId, UserId};
use domain::{DomainError, DomainResult};
const OWNERSHIP_DENIED: &str = "You don't own this channel";
pub(crate) async fn find_owned_channel(
query: &dyn domain::ports::ChannelQuery,
channel_id: ChannelId,
owner_id: UserId,
raw_channel_id: uuid::Uuid,
) -> DomainResult<Channel> {
let channel = query
.find_by_id(channel_id)
.await?
.ok_or(DomainError::ChannelNotFound(raw_channel_id))?;
if channel.owner_id() != owner_id {
return Err(DomainError::forbidden(OWNERSHIP_DENIED));
}
Ok(channel)
}

View File

@@ -1,14 +1,11 @@
use uuid::Uuid;
/// Fetch a single channel by ID.
pub struct GetChannelQuery {
pub channel_id: Uuid,
}
/// List all channels.
pub struct ListChannelsQuery;
/// List channels belonging to a specific owner.
pub struct ListByOwnerQuery {
pub owner_id: Uuid,
}

View File

@@ -1,38 +1,26 @@
use domain::events::DomainEvent;
use domain::models::Channel;
use domain::value_objects::{ChannelId, UserId};
use domain::{DomainError, DomainResult};
use domain::DomainResult;
use super::commands::UpdateChannelCommand;
use super::deps::ChannelCommandDeps;
use super::find_owned_channel;
/// Update an existing channel.
///
/// Flow: find channel -> verify ownership -> snapshot config if changed ->
/// apply updates -> save -> publish event -> return.
pub async fn execute(deps: &ChannelCommandDeps, cmd: UpdateChannelCommand) -> DomainResult<Channel> {
let channel_id = ChannelId::from(cmd.channel_id);
let owner_id = UserId::from(cmd.owner_id);
let mut channel = deps
.channel_query
.find_by_id(channel_id)
.await?
.ok_or(DomainError::ChannelNotFound(cmd.channel_id))?;
let mut channel =
find_owned_channel(deps.channel_query.as_ref(), channel_id, owner_id, cmd.channel_id)
.await?;
// Ownership check
if channel.owner_id() != owner_id {
return Err(DomainError::forbidden("You don't own this channel"));
}
// Auto-snapshot the current config before overwriting
if cmd.schedule_config.is_some() {
deps.channel_command
.save_config_snapshot(channel_id, channel.schedule_config(), None)
.await?;
}
// Apply partial updates
if let Some(name) = cmd.name {
channel.set_name(name);
}

View File

@@ -1,19 +1,16 @@
use uuid::Uuid;
/// Save a snapshot of the channel's current config.
pub struct SaveSnapshotCommand {
pub channel_id: Uuid,
pub label: Option<String>,
}
/// Update the label on an existing snapshot.
pub struct PatchLabelCommand {
pub channel_id: Uuid,
pub snapshot_id: Uuid,
pub label: Option<String>,
}
/// Restore a channel's config from a snapshot.
pub struct RestoreSnapshotCommand {
pub channel_id: Uuid,
pub snapshot_id: Uuid,

View File

@@ -2,7 +2,6 @@ use std::sync::Arc;
use domain::ports::{ChannelCommand, ChannelQuery};
/// Dependencies for config snapshot use cases.
pub struct ConfigSnapshotDeps {
pub channel_command: Arc<dyn ChannelCommand>,
pub channel_query: Arc<dyn ChannelQuery>,

View File

@@ -5,7 +5,6 @@ use domain::DomainResult;
use super::deps::ConfigSnapshotDeps;
use super::queries::GetSnapshotQuery;
/// Get a specific config snapshot by channel and snapshot ID.
pub async fn execute(
deps: &ConfigSnapshotDeps,
query: GetSnapshotQuery,

View File

@@ -5,7 +5,6 @@ use domain::DomainResult;
use super::deps::ConfigSnapshotDeps;
use super::queries::ListSnapshotsQuery;
/// List all config snapshots for a channel, newest first.
pub async fn execute(
deps: &ConfigSnapshotDeps,
query: ListSnapshotsQuery,

View File

@@ -5,7 +5,6 @@ use domain::DomainResult;
use super::commands::PatchLabelCommand;
use super::deps::ConfigSnapshotDeps;
/// Update the label on an existing config snapshot.
pub async fn execute(
deps: &ConfigSnapshotDeps,
cmd: PatchLabelCommand,

View File

@@ -1,11 +1,9 @@
use uuid::Uuid;
/// List all config snapshots for a channel (newest first).
pub struct ListSnapshotsQuery {
pub channel_id: Uuid,
}
/// Get a specific config snapshot.
pub struct GetSnapshotQuery {
pub channel_id: Uuid,
pub snapshot_id: Uuid,

View File

@@ -5,10 +5,6 @@ use domain::{DomainError, DomainResult};
use super::commands::RestoreSnapshotCommand;
use super::deps::ConfigSnapshotDeps;
/// Restore a channel's config from a snapshot.
///
/// Flow: find snapshot -> find channel -> snapshot current config (backup) ->
/// apply snapshot config to channel -> save channel -> return updated channel.
pub async fn execute(
deps: &ConfigSnapshotDeps,
cmd: RestoreSnapshotCommand,
@@ -30,12 +26,10 @@ pub async fn execute(
.await?
.ok_or(DomainError::ChannelNotFound(cmd.channel_id))?;
// Auto-snapshot the current config before overwriting
deps.channel_command
.save_config_snapshot(channel_id, channel.schedule_config(), None)
.await?;
// Apply the snapshot's config
channel.set_schedule_config(snapshot.config().clone());
deps.channel_command.save(&channel).await?;

View File

@@ -5,9 +5,6 @@ use domain::{DomainError, DomainResult};
use super::commands::SaveSnapshotCommand;
use super::deps::ConfigSnapshotDeps;
/// Save a snapshot of the channel's current schedule config.
///
/// Flow: find channel -> snapshot its current config -> return snapshot.
pub async fn execute(
deps: &ConfigSnapshotDeps,
cmd: SaveSnapshotCommand,

View File

@@ -2,7 +2,6 @@ use std::sync::Arc;
use domain::ports::{ChannelQuery, ScheduleQuery};
/// Dependencies for IPTV export use cases.
pub struct IptvDeps {
pub channel_query: Arc<dyn ChannelQuery>,
pub schedule_query: Arc<dyn ScheduleQuery>,

View File

@@ -4,9 +4,6 @@ use domain::DomainResult;
use super::deps::IptvDeps;
use super::queries::GetM3uQuery;
/// Generate an M3U playlist for all channels.
///
/// Flow: fetch all channels -> delegate to domain::generate_m3u -> return string.
pub async fn execute(deps: &IptvDeps, query: GetM3uQuery) -> DomainResult<String> {
let channels = deps.channel_query.find_all().await?;
let token = query.token.as_deref().unwrap_or("");

View File

@@ -1,8 +1,6 @@
/// Generate an M3U playlist for all channels.
pub struct GetM3uQuery {
pub base_url: String,
pub token: Option<String>,
}
/// Generate an XMLTV EPG document for all channels.
pub struct GetXmltvQuery;

View File

@@ -8,10 +8,6 @@ use domain::DomainResult;
use super::deps::IptvDeps;
use super::queries::GetXmltvQuery;
/// Generate an XMLTV EPG document for all channels with active schedules.
///
/// Flow: fetch all channels -> for each, find active schedule -> collect slots
/// -> delegate to domain::generate_xmltv -> return string.
pub async fn execute(deps: &IptvDeps, _query: GetXmltvQuery) -> DomainResult<String> {
let channels = deps.channel_query.find_all().await?;
let now = Utc::now();

View File

@@ -1,5 +1,3 @@
/// Trigger a library sync for one or all providers.
pub struct TriggerSyncCommand {
/// Provider to sync. `None` means sync all registered providers.
pub provider_id: Option<String>,
}

View File

@@ -2,7 +2,6 @@ use std::sync::Arc;
use domain::ports::{EventPublisher, IProviderRegistry, LibraryCommand, LibraryQuery, LibrarySyncAdapter};
/// Dependencies for library write use cases (trigger sync).
pub struct LibraryCommandDeps {
pub library_command: Arc<dyn LibraryCommand>,
pub library_query: Arc<dyn LibraryQuery>,
@@ -11,7 +10,6 @@ pub struct LibraryCommandDeps {
pub event_publisher: Arc<dyn EventPublisher>,
}
/// Dependencies for library read use cases (search, list, get).
pub struct LibraryQueryDeps {
pub library_query: Arc<dyn LibraryQuery>,
}

View File

@@ -4,7 +4,6 @@ use domain::DomainResult;
use super::deps::LibraryQueryDeps;
use super::queries::GetItemQuery;
/// Get a single library item by its composite ID.
pub async fn execute(
deps: &LibraryQueryDeps,
query: GetItemQuery,

View File

@@ -4,7 +4,6 @@ use domain::DomainResult;
use super::deps::LibraryQueryDeps;
use super::queries::GetSyncStatusQuery;
/// Get the latest sync status per provider.
pub async fn execute(
deps: &LibraryQueryDeps,
_query: GetSyncStatusQuery,

View File

@@ -4,7 +4,6 @@ use domain::DomainResult;
use super::deps::LibraryQueryDeps;
use super::queries::ListCollectionsQuery;
/// List library collections, optionally filtered by provider.
pub async fn execute(
deps: &LibraryQueryDeps,
query: ListCollectionsQuery,

View File

@@ -1,10 +1,9 @@
use domain::errors::{DomainError, DomainResult};
use domain::value_objects::ContentType;
use domain::DomainResult;
use super::deps::LibraryQueryDeps;
use super::parse_content_type;
use super::queries::ListGenresQuery;
/// List genres available in the library, optionally filtered.
pub async fn execute(deps: &LibraryQueryDeps, query: ListGenresQuery) -> DomainResult<Vec<String>> {
let content_type = query
.content_type
@@ -17,17 +16,6 @@ pub async fn execute(deps: &LibraryQueryDeps, query: ListGenresQuery) -> DomainR
.await
}
fn parse_content_type(s: &str) -> DomainResult<ContentType> {
match s {
"movie" => Ok(ContentType::Movie),
"episode" => Ok(ContentType::Episode),
"short" => Ok(ContentType::Short),
other => Err(DomainError::ValidationError(format!(
"Unknown content type '{other}'. Use movie, episode, or short."
))),
}
}
#[cfg(test)]
#[path = "tests/list_genres.rs"]
mod tests;

View File

@@ -4,7 +4,6 @@ use domain::DomainResult;
use super::deps::LibraryQueryDeps;
use super::queries::ListSeasonsQuery;
/// List season summaries for a specific series.
pub async fn execute(
deps: &LibraryQueryDeps,
query: ListSeasonsQuery,

View File

@@ -4,7 +4,6 @@ use domain::DomainResult;
use super::deps::LibraryQueryDeps;
use super::queries::ListShowsQuery;
/// List TV show summaries, optionally filtered.
pub async fn execute(
deps: &LibraryQueryDeps,
query: ListShowsQuery,

View File

@@ -16,3 +16,17 @@ pub use queries::{
GetItemQuery, GetSyncStatusQuery, ListCollectionsQuery, ListGenresQuery, ListSeasonsQuery,
ListShowsQuery, SearchItemsQuery,
};
use domain::errors::{DomainError, DomainResult};
use domain::value_objects::ContentType;
pub(crate) fn parse_content_type(s: &str) -> DomainResult<ContentType> {
match s {
"movie" => Ok(ContentType::Movie),
"episode" => Ok(ContentType::Episode),
"short" => Ok(ContentType::Short),
other => Err(DomainError::ValidationError(format!(
"Unknown content type '{other}'. Use movie, episode, or short."
))),
}
}

View File

@@ -1,4 +1,3 @@
/// Search library items with filters.
pub struct SearchItemsQuery {
pub provider_id: Option<String>,
pub content_type: Option<String>,
@@ -12,34 +11,28 @@ pub struct SearchItemsQuery {
pub limit: u32,
}
/// List library collections.
pub struct ListCollectionsQuery {
pub provider_id: Option<String>,
}
/// List TV show summaries.
pub struct ListShowsQuery {
pub provider_id: Option<String>,
pub search_term: Option<String>,
pub genres: Vec<String>,
}
/// List seasons for a specific series.
pub struct ListSeasonsQuery {
pub series_name: String,
pub provider_id: Option<String>,
}
/// List genres available in the library.
pub struct ListGenresQuery {
pub content_type: Option<String>,
pub provider_id: Option<String>,
}
/// Get a single library item by its composite ID.
pub struct GetItemQuery {
pub item_id: String,
}
/// Get the latest sync status per provider.
pub struct GetSyncStatusQuery;

View File

@@ -1,11 +1,11 @@
use domain::errors::{DomainError, DomainResult};
use domain::DomainResult;
use domain::models::LibraryItem;
use domain::value_objects::{ContentType, LibrarySearchFilter};
use domain::value_objects::LibrarySearchFilter;
use super::deps::LibraryQueryDeps;
use super::parse_content_type;
use super::queries::SearchItemsQuery;
/// Search library items with filters. Returns `(items, total_count)`.
pub async fn execute(
deps: &LibraryQueryDeps,
query: SearchItemsQuery,
@@ -48,17 +48,6 @@ pub async fn execute(
deps.library_query.search(&filter).await
}
fn parse_content_type(s: &str) -> DomainResult<ContentType> {
match s {
"movie" => Ok(ContentType::Movie),
"episode" => Ok(ContentType::Episode),
"short" => Ok(ContentType::Short),
other => Err(DomainError::ValidationError(format!(
"Unknown content type '{other}'. Use movie, episode, or short."
))),
}
}
#[cfg(test)]
#[path = "tests/search.rs"]
mod tests;

View File

@@ -3,15 +3,6 @@ use domain::errors::{DomainError, DomainResult};
use super::commands::TriggerSyncCommand;
use super::deps::LibraryCommandDeps;
/// Validate and return provider IDs eligible for sync.
///
/// Checks that no sync is already running for the targeted provider(s).
/// Returns the list of provider IDs to sync. The caller (API layer) is
/// responsible for spawning the actual sync tasks, since `LibrarySyncAdapter`
/// requires `&dyn IMediaProvider` references that only the infra layer holds.
///
/// Returns `Err(ValidationError)` if any targeted provider is already syncing
/// (maps to 409 Conflict at the API layer).
pub async fn execute(
deps: &LibraryCommandDeps,
cmd: TriggerSyncCommand,

View File

@@ -1,4 +1,3 @@
/// Insert or update a provider configuration.
pub struct UpsertProviderCommand {
pub id: String,
pub provider_type: String,
@@ -6,7 +5,6 @@ pub struct UpsertProviderCommand {
pub enabled: bool,
}
/// Delete a provider configuration.
pub struct DeleteProviderCommand {
pub id: String,
}

View File

@@ -3,7 +3,6 @@ use domain::DomainResult;
use super::commands::DeleteProviderCommand;
use super::deps::ProviderDeps;
/// Delete a provider configuration by ID.
pub async fn execute(deps: &ProviderDeps, cmd: DeleteProviderCommand) -> DomainResult<()> {
deps.provider_config_command.delete(&cmd.id).await
}

View File

@@ -2,7 +2,6 @@ use std::sync::Arc;
use domain::ports::{ProviderConfigCommand, ProviderConfigQuery};
/// Dependencies for provider config use cases.
pub struct ProviderDeps {
pub provider_config_command: Arc<dyn ProviderConfigCommand>,
pub provider_config_query: Arc<dyn ProviderConfigQuery>,

View File

@@ -4,7 +4,6 @@ use domain::DomainResult;
use super::deps::ProviderDeps;
use super::queries::GetProviderQuery;
/// Get a provider configuration by ID.
pub async fn execute(
deps: &ProviderDeps,
query: GetProviderQuery,

View File

@@ -4,7 +4,6 @@ use domain::DomainResult;
use super::deps::ProviderDeps;
use super::queries::ListProvidersQuery;
/// List all provider configurations.
pub async fn execute(
deps: &ProviderDeps,
_query: ListProvidersQuery,

View File

@@ -1,7 +1,5 @@
/// List all provider configurations.
pub struct ListProvidersQuery;
/// Get a provider configuration by ID.
pub struct GetProviderQuery {
pub id: String,
}

View File

@@ -4,7 +4,6 @@ use domain::DomainResult;
use super::commands::UpsertProviderCommand;
use super::deps::ProviderDeps;
/// Insert or update a provider configuration.
pub async fn execute(deps: &ProviderDeps, cmd: UpsertProviderCommand) -> DomainResult<()> {
let row = ProviderConfigRow::from_persistence(
cmd.id,

View File

@@ -1,11 +1,9 @@
use uuid::Uuid;
/// Generate a new schedule for a channel.
pub struct GenerateScheduleCommand {
pub channel_id: Uuid,
}
/// Delete all schedules with generation > target_generation for a channel.
pub struct DeleteSchedulesAfterCommand {
pub channel_id: Uuid,
pub target_generation: u32,

View File

@@ -4,7 +4,6 @@ use domain::DomainResult;
use super::commands::DeleteSchedulesAfterCommand;
use super::deps::ScheduleDeps;
/// Delete all schedules with generation > target_generation for a channel.
pub async fn execute(
deps: &ScheduleDeps,
cmd: DeleteSchedulesAfterCommand,

View File

@@ -3,7 +3,6 @@ use std::sync::Arc;
use domain::ports::{ChannelQuery, EventPublisher, ScheduleCommand, ScheduleQuery};
use domain::ScheduleEngineService;
/// Dependencies for schedule use cases.
pub struct ScheduleDeps {
pub schedule_engine: Arc<ScheduleEngineService>,
pub channel_query: Arc<dyn ChannelQuery>,

View File

@@ -8,10 +8,6 @@ use domain::DomainResult;
use super::commands::GenerateScheduleCommand;
use super::deps::ScheduleDeps;
/// Generate a new 7-day schedule for a channel.
///
/// Delegates the heavy lifting to `ScheduleEngineService::generate_schedule`,
/// then publishes a `ScheduleGenerated` domain event.
pub async fn execute(
deps: &ScheduleDeps,
cmd: GenerateScheduleCommand,

View File

@@ -7,9 +7,6 @@ use domain::DomainResult;
use super::deps::ScheduleDeps;
use super::queries::GetActiveScheduleQuery;
/// Fetch the schedule currently active at `now`.
///
/// Returns `None` when no schedule covers the current time.
pub async fn execute(
deps: &ScheduleDeps,
query: GetActiveScheduleQuery,

View File

@@ -7,10 +7,6 @@ use domain::{DomainResult, ScheduleEngineService};
use super::deps::ScheduleDeps;
use super::queries::GetCurrentBroadcastQuery;
/// Determine what is currently broadcasting on a channel.
///
/// Returns `None` when no schedule is active or `now` falls in a gap
/// between blocks (no-signal / static screen).
pub async fn execute(
deps: &ScheduleDeps,
query: GetCurrentBroadcastQuery,

View File

@@ -7,10 +7,6 @@ use domain::{DomainResult, ScheduleEngineService};
use super::deps::ScheduleDeps;
use super::queries::GetEpgQuery;
/// Return EPG (electronic program guide) data for a channel.
///
/// Returns the slots that overlap the active schedule's validity window.
/// Returns an empty vec when no schedule is active.
pub async fn execute(
deps: &ScheduleDeps,
query: GetEpgQuery,

View File

@@ -5,9 +5,6 @@ use domain::DomainResult;
use super::deps::ScheduleDeps;
use super::queries::GetStreamUrlQuery;
/// Resolve a playback URL for a media item.
///
/// Delegates to the schedule engine which routes via the provider registry.
pub async fn execute(deps: &ScheduleDeps, query: GetStreamUrlQuery) -> DomainResult<String> {
let item_id = MediaItemId::new(&query.item_id);
deps.schedule_engine

View File

@@ -5,7 +5,6 @@ use domain::DomainResult;
use super::deps::ScheduleDeps;
use super::queries::ListHistoryQuery;
/// List all generated schedule headers for a channel, newest first.
pub async fn execute(
deps: &ScheduleDeps,
query: ListHistoryQuery,

View File

@@ -1,28 +1,22 @@
use uuid::Uuid;
/// Fetch the schedule whose validity window contains `now`.
pub struct GetActiveScheduleQuery {
pub channel_id: Uuid,
}
/// Determine what is currently broadcasting on a channel.
pub struct GetCurrentBroadcastQuery {
pub channel_id: Uuid,
}
/// Return EPG (electronic program guide) data for a channel.
pub struct GetEpgQuery {
pub channel_id: Uuid,
}
/// Get a playback URL for a specific media item on a channel.
pub struct GetStreamUrlQuery {
pub channel_id: Uuid,
/// MediaItemId as string (e.g. "jellyfin::abc123").
pub item_id: String,
}
/// List all generated schedule headers for a channel.
pub struct ListHistoryQuery {
pub channel_id: Uuid,
}