application: config_snapshots, admin, providers, iptv
This commit is contained in:
9
crates/application/src/iptv/deps.rs
Normal file
9
crates/application/src/iptv/deps.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
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>,
|
||||
}
|
||||
18
crates/application/src/iptv/m3u.rs
Normal file
18
crates/application/src/iptv/m3u.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use domain::services::iptv::generate_m3u;
|
||||
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("");
|
||||
Ok(generate_m3u(&channels, &query.base_url, token))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/m3u.rs"]
|
||||
mod tests;
|
||||
7
crates/application/src/iptv/mod.rs
Normal file
7
crates/application/src/iptv/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
pub mod deps;
|
||||
pub mod m3u;
|
||||
pub mod queries;
|
||||
pub mod xmltv;
|
||||
|
||||
pub use deps::IptvDeps;
|
||||
pub use queries::{GetM3uQuery, GetXmltvQuery};
|
||||
8
crates/application/src/iptv/queries.rs
Normal file
8
crates/application/src/iptv/queries.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
/// 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;
|
||||
84
crates/application/src/iptv/tests/m3u.rs
Normal file
84
crates/application/src/iptv/tests/m3u.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::models::Channel;
|
||||
use domain::testing::{InMemoryChannelRepository, InMemoryScheduleRepository};
|
||||
use domain::value_objects::UserId;
|
||||
|
||||
use crate::iptv::deps::IptvDeps;
|
||||
use crate::iptv::m3u;
|
||||
use crate::iptv::queries::GetM3uQuery;
|
||||
|
||||
fn make_deps() -> (IptvDeps, Arc<InMemoryChannelRepository>) {
|
||||
let channel_repo = Arc::new(InMemoryChannelRepository::new());
|
||||
let schedule_repo = Arc::new(InMemoryScheduleRepository::new());
|
||||
let deps = IptvDeps {
|
||||
channel_query: channel_repo.clone(),
|
||||
schedule_query: schedule_repo,
|
||||
};
|
||||
(deps, channel_repo)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn m3u_empty_channels() {
|
||||
let (deps, _) = make_deps();
|
||||
|
||||
let result = m3u::execute(
|
||||
&deps,
|
||||
GetM3uQuery {
|
||||
base_url: "http://localhost:3000".into(),
|
||||
token: Some("tok123".into()),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result, "#EXTM3U\n");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn m3u_includes_channels() {
|
||||
let (deps, repo) = make_deps();
|
||||
|
||||
let ch = Channel::new(UserId::generate(), "Test TV", "UTC");
|
||||
repo.channels
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(ch.id().value(), ch.clone());
|
||||
|
||||
let result = m3u::execute(
|
||||
&deps,
|
||||
GetM3uQuery {
|
||||
base_url: "http://localhost:3000".into(),
|
||||
token: Some("mytoken".into()),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(result.starts_with("#EXTM3U\n"));
|
||||
assert!(result.contains("Test TV"));
|
||||
assert!(result.contains("token=mytoken"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn m3u_no_token() {
|
||||
let (deps, repo) = make_deps();
|
||||
|
||||
let ch = Channel::new(UserId::generate(), "Ch1", "UTC");
|
||||
repo.channels
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(ch.id().value(), ch);
|
||||
|
||||
let result = m3u::execute(
|
||||
&deps,
|
||||
GetM3uQuery {
|
||||
base_url: "http://localhost:3000".into(),
|
||||
token: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(result.contains("token="));
|
||||
}
|
||||
27
crates/application/src/iptv/xmltv.rs
Normal file
27
crates/application/src/iptv/xmltv.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use chrono::Utc;
|
||||
|
||||
use domain::services::iptv::generate_xmltv;
|
||||
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();
|
||||
|
||||
let mut slots_by_channel = HashMap::new();
|
||||
for ch in &channels {
|
||||
if let Some(schedule) = deps.schedule_query.find_active(ch.id(), now).await? {
|
||||
slots_by_channel.insert(ch.id(), schedule.slots().to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(generate_xmltv(&channels, &slots_by_channel))
|
||||
}
|
||||
Reference in New Issue
Block a user