use axum::Json; use axum::extract::{Path, State}; use axum::http::{StatusCode, header}; use axum::response::IntoResponse; use chrono::Utc; use api_types::{ CurrentBroadcastResponse, ScheduleHistoryEntry, ScheduleResponse, SlotResponse, }; use application::schedule::{ GenerateScheduleCommand, GetCurrentBroadcastQuery, GetEpgQuery, GetSourceQuery, }; use domain::DomainError; use domain::value_objects::ChannelId; use crate::errors::AppError; use crate::extractors::CurrentUser; use crate::state::AppState; pub async fn generate_schedule( State(state): State, CurrentUser(_user): CurrentUser, Path(id): Path, ) -> Result, AppError> { let cmd = GenerateScheduleCommand { channel_id: id }; let schedule = application::schedule::generate::execute(&state.schedule_deps, cmd).await?; Ok(Json(ScheduleResponse::from(schedule))) } pub async fn get_active_schedule( State(state): State, CurrentUser(_user): CurrentUser, Path(id): Path, ) -> Result { let channel_id = ChannelId::from(id); match state.schedule_query.find_active(channel_id, Utc::now()).await? { Some(schedule) => Ok(Json(ScheduleResponse::from(schedule)).into_response()), None => Ok(StatusCode::NO_CONTENT.into_response()), } } pub async fn get_current_broadcast( State(state): State, Path(id): Path, ) -> Result { let query = GetCurrentBroadcastQuery { channel_id: id }; match application::schedule::get_current_broadcast::execute(&state.schedule_deps, query).await? { Some(result) => { let slot_response = SlotResponse::from(result.broadcast.slot().clone()); Ok(Json(CurrentBroadcastResponse { slot: slot_response, offset_secs: result.broadcast.offset_secs(), }) .into_response()) } None => Ok(StatusCode::NO_CONTENT.into_response()), } } pub async fn get_epg( State(state): State, Path(id): Path, ) -> Result>, AppError> { let query = GetEpgQuery { channel_id: id }; let slots = application::schedule::get_epg::execute(&state.schedule_deps, query).await?; Ok(Json(slots.into_iter().map(SlotResponse::from).collect())) } pub async fn get_stream( State(state): State, Path(id): Path, ) -> Result { let query = GetSourceQuery { channel_id: id }; match application::schedule::get_source::execute(&state.schedule_deps, query).await? { Some(uri) => Ok(Json(uri).into_response()), None => Ok(StatusCode::NO_CONTENT.into_response()), } } pub async fn list_schedule_history( State(state): State, CurrentUser(_user): CurrentUser, Path(id): Path, ) -> Result>, AppError> { let channel_id = ChannelId::from(id); let history = state.schedule_query.list_schedule_history(channel_id).await?; Ok(Json( history .into_iter() .map(ScheduleHistoryEntry::from) .collect(), )) } pub async fn export_ical( State(state): State, Path(id): Path, ) -> Result { let channel = state .channel_query .find_by_id(id.into()) .await? .ok_or_else(|| AppError(DomainError::NotFound(format!("Channel {id} not found"))))?; let ical = domain::generate_ical(channel.name(), channel.timezone(), channel.schedule_config()); let disposition = format!("attachment; filename=\"{}.ics\"", channel.name()); Ok(( [ (header::CONTENT_TYPE, "text/calendar; charset=utf-8".to_string()), (header::CONTENT_DISPOSITION, disposition), ], ical, ) .into_response()) }