application: config_snapshots, admin, providers, iptv

This commit is contained in:
2026-07-12 02:14:07 +02:00
parent ebf0614fdf
commit 466d34b5d0
37 changed files with 895 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
/// Insert or update a provider configuration.
pub struct UpsertProviderCommand {
pub id: String,
pub provider_type: String,
pub config_json: String,
pub enabled: bool,
}
/// Delete a provider configuration.
pub struct DeleteProviderCommand {
pub id: String,
}

View File

@@ -0,0 +1,9 @@
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

@@ -0,0 +1,9 @@
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

@@ -0,0 +1,13 @@
use domain::models::ProviderConfigRow;
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,
) -> DomainResult<Option<ProviderConfigRow>> {
deps.provider_config_query.get_by_id(&query.id).await
}

View File

@@ -0,0 +1,17 @@
use domain::models::ProviderConfigRow;
use domain::DomainResult;
use super::deps::ProviderDeps;
use super::queries::ListProvidersQuery;
/// List all provider configurations.
pub async fn execute(
deps: &ProviderDeps,
_query: ListProvidersQuery,
) -> DomainResult<Vec<ProviderConfigRow>> {
deps.provider_config_query.get_all().await
}
#[cfg(test)]
#[path = "tests/list.rs"]
mod tests;

View File

@@ -0,0 +1,11 @@
pub mod commands;
pub mod delete;
pub mod deps;
pub mod get;
pub mod list;
pub mod queries;
pub mod upsert;
pub use commands::{DeleteProviderCommand, UpsertProviderCommand};
pub use deps::ProviderDeps;
pub use queries::{GetProviderQuery, ListProvidersQuery};

View File

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

View File

@@ -0,0 +1,45 @@
use std::sync::Arc;
use domain::testing::InMemoryProviderConfig;
use crate::providers::commands::UpsertProviderCommand;
use crate::providers::deps::ProviderDeps;
use crate::providers::queries::ListProvidersQuery;
use crate::providers::{list, upsert};
fn make_deps() -> ProviderDeps {
let repo = Arc::new(InMemoryProviderConfig::new());
ProviderDeps {
provider_config_command: repo.clone(),
provider_config_query: repo,
}
}
#[tokio::test]
async fn list_empty() {
let deps = make_deps();
let providers = list::execute(&deps, ListProvidersQuery).await.unwrap();
assert!(providers.is_empty());
}
#[tokio::test]
async fn list_returns_all_providers() {
let deps = make_deps();
for id in ["jf-1", "local-1"] {
upsert::execute(
&deps,
UpsertProviderCommand {
id: id.into(),
provider_type: "jellyfin".into(),
config_json: "{}".into(),
enabled: true,
},
)
.await
.unwrap();
}
let providers = list::execute(&deps, ListProvidersQuery).await.unwrap();
assert_eq!(providers.len(), 2);
}

View File

@@ -0,0 +1,76 @@
use std::sync::Arc;
use domain::testing::InMemoryProviderConfig;
use crate::providers::commands::UpsertProviderCommand;
use crate::providers::deps::ProviderDeps;
use crate::providers::upsert;
fn make_deps() -> ProviderDeps {
let repo = Arc::new(InMemoryProviderConfig::new());
ProviderDeps {
provider_config_command: repo.clone(),
provider_config_query: repo,
}
}
#[tokio::test]
async fn upsert_stores_provider() {
let deps = make_deps();
upsert::execute(
&deps,
UpsertProviderCommand {
id: "jf-1".into(),
provider_type: "jellyfin".into(),
config_json: r#"{"url":"http://localhost:8096"}"#.into(),
enabled: true,
},
)
.await
.unwrap();
let row = deps.provider_config_query.get_by_id("jf-1").await.unwrap();
assert!(row.is_some());
let row = row.unwrap();
assert_eq!(row.provider_type(), "jellyfin");
assert!(row.enabled());
}
#[tokio::test]
async fn upsert_overwrites_existing() {
let deps = make_deps();
upsert::execute(
&deps,
UpsertProviderCommand {
id: "jf-1".into(),
provider_type: "jellyfin".into(),
config_json: r#"{"url":"http://old"}"#.into(),
enabled: true,
},
)
.await
.unwrap();
upsert::execute(
&deps,
UpsertProviderCommand {
id: "jf-1".into(),
provider_type: "jellyfin".into(),
config_json: r#"{"url":"http://new"}"#.into(),
enabled: false,
},
)
.await
.unwrap();
let row = deps
.provider_config_query
.get_by_id("jf-1")
.await
.unwrap()
.unwrap();
assert!(row.config_json().contains("new"));
assert!(!row.enabled());
}

View File

@@ -0,0 +1,21 @@
use domain::models::ProviderConfigRow;
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,
cmd.provider_type,
cmd.config_json,
cmd.enabled,
String::new(),
);
deps.provider_config_command.upsert(&row).await
}
#[cfg(test)]
#[path = "tests/upsert.rs"]
mod tests;