iCalendar import: parse_ical + API + MCP tool (#16)

This commit is contained in:
2026-07-12 14:46:15 +02:00
parent 6b0d060945
commit a7fa2ec4aa
9 changed files with 565 additions and 7 deletions

View File

@@ -14,7 +14,7 @@ use schemars::JsonSchema;
use serde::Deserialize;
use uuid::Uuid;
use crate::tools::{channels, library, schedule};
use crate::tools::{channels, ical, library, schedule};
const SERVER_NAME: &str = "k-tv-mcp";
@@ -151,6 +151,12 @@ pub struct SetGapFillerParams {
pub filter_json: Option<String>,
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ImportIcalParams {
pub channel_id: String,
pub ical_string: String,
}
fn parse_uuid(s: &str) -> Result<Uuid, String> {
s.parse::<Uuid>()
.map_err(|_| serde_json::json!({"error": format!("invalid UUID: {s}")}).to_string())
@@ -423,6 +429,29 @@ impl KTvMcpServer {
)
.await
}
#[tool(
description = "Export a channel's ScheduleConfig as an iCalendar (.ics) string per RFC 5545."
)]
async fn export_schedule_ical(&self, #[tool(aggr)] p: ChannelIdParam) -> String {
match parse_uuid(&p.channel_id) {
Ok(id) => ical::export_schedule_ical(&self.channel_query, id).await,
Err(e) => e,
}
}
#[tool(
description = "Import an iCalendar (.ics) string to replace a channel's ScheduleConfig. VEVENTs map to ProgrammingBlocks."
)]
async fn import_schedule_ical(&self, #[tool(aggr)] p: ImportIcalParams) -> String {
match parse_uuid(&p.channel_id) {
Ok(id) => {
ical::import_schedule_ical(&self.channel_cmd_deps, id, self.owner_id, &p.ical_string)
.await
}
Err(e) => e,
}
}
}
#[tool(tool_box)]

View File

@@ -1,8 +1,9 @@
use std::sync::Arc;
use application::channels::{ChannelCommandDeps, UpdateChannelCommand};
use uuid::Uuid;
use crate::error::domain_err;
use crate::error::{domain_err, ok_json};
pub async fn export_schedule_ical(
channel_query: &Arc<dyn domain::ports::ChannelQuery>,
@@ -18,3 +19,30 @@ pub async fn export_schedule_ical(
Err(e) => domain_err(e),
}
}
pub async fn import_schedule_ical(
cmd_deps: &Arc<ChannelCommandDeps>,
channel_id: Uuid,
owner_id: Uuid,
ical_str: &str,
) -> String {
let config = match domain::parse_ical(ical_str) {
Ok(c) => c,
Err(e) => return domain_err(e),
};
let cmd = UpdateChannelCommand {
channel_id: channel_id.into(),
owner_id: owner_id.into(),
name: None,
description: None,
timezone: None,
schedule_config: Some(config),
rotation_policy: None,
auto_schedule: None,
gap_filler: None,
};
match application::channels::update::execute(cmd_deps, cmd).await {
Ok(channel) => ok_json(&channel),
Err(e) => domain_err(e),
}
}