Files
k-tv/crates/presentation/src/handlers/schedule.rs

118 lines
3.9 KiB
Rust

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<AppState>,
CurrentUser(_user): CurrentUser,
Path(id): Path<uuid::Uuid>,
) -> Result<Json<ScheduleResponse>, 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<AppState>,
CurrentUser(_user): CurrentUser,
Path(id): Path<uuid::Uuid>,
) -> Result<axum::response::Response, AppError> {
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<AppState>,
Path(id): Path<uuid::Uuid>,
) -> Result<axum::response::Response, AppError> {
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<AppState>,
Path(id): Path<uuid::Uuid>,
) -> Result<Json<Vec<SlotResponse>>, 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<AppState>,
Path(id): Path<uuid::Uuid>,
) -> Result<axum::response::Response, AppError> {
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<AppState>,
CurrentUser(_user): CurrentUser,
Path(id): Path<uuid::Uuid>,
) -> Result<Json<Vec<ScheduleHistoryEntry>>, 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<AppState>,
Path(id): Path<uuid::Uuid>,
) -> Result<axum::response::Response, AppError> {
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())
}