feat: schedule history — list, detail, rollback endpoints
This commit is contained in:
@@ -27,6 +27,15 @@ pub fn router() -> Router<AppState> {
|
||||
"/{id}/schedule",
|
||||
post(schedule::generate_schedule).get(schedule::get_active_schedule),
|
||||
)
|
||||
.route("/{id}/schedule/history", get(schedule::list_schedule_history))
|
||||
.route(
|
||||
"/{id}/schedule/history/{gen_id}",
|
||||
get(schedule::get_schedule_history_entry),
|
||||
)
|
||||
.route(
|
||||
"/{id}/schedule/history/{gen_id}/rollback",
|
||||
post(schedule::rollback_schedule),
|
||||
)
|
||||
.route("/{id}/now", get(broadcast::get_current_broadcast))
|
||||
.route("/{id}/epg", get(broadcast::get_epg))
|
||||
.route("/{id}/stream", get(broadcast::get_stream))
|
||||
|
||||
@@ -10,7 +10,7 @@ use uuid::Uuid;
|
||||
use domain::{self, DomainError};
|
||||
|
||||
use crate::{
|
||||
dto::ScheduleResponse,
|
||||
dto::{ScheduleHistoryEntry, ScheduleResponse},
|
||||
error::ApiError,
|
||||
extractors::CurrentUser,
|
||||
state::AppState,
|
||||
@@ -60,3 +60,75 @@ pub(super) async fn get_active_schedule(
|
||||
|
||||
Ok(Json(ScheduleResponse::from(schedule)))
|
||||
}
|
||||
|
||||
/// List all schedule generations for a channel, newest first.
|
||||
/// Returns lightweight entries (no slots).
|
||||
pub(super) async fn list_schedule_history(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(user): CurrentUser,
|
||||
Path(channel_id): Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
let channel = state.channel_service.find_by_id(channel_id).await?;
|
||||
require_owner(&channel, user.id)?;
|
||||
|
||||
let history = state.schedule_engine.list_schedule_history(channel_id).await?;
|
||||
let entries: Vec<ScheduleHistoryEntry> = history.into_iter().map(Into::into).collect();
|
||||
Ok(Json(entries))
|
||||
}
|
||||
|
||||
/// Fetch a single historical schedule with all its slots.
|
||||
pub(super) async fn get_schedule_history_entry(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(user): CurrentUser,
|
||||
Path((channel_id, gen_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
let channel = state.channel_service.find_by_id(channel_id).await?;
|
||||
require_owner(&channel, user.id)?;
|
||||
|
||||
let schedule = state
|
||||
.schedule_engine
|
||||
.get_schedule_by_id(channel_id, gen_id)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Schedule {} not found", gen_id)))?;
|
||||
|
||||
Ok(Json(ScheduleResponse::from(schedule)))
|
||||
}
|
||||
|
||||
/// Roll back to a previous schedule generation.
|
||||
///
|
||||
/// Deletes all generations after `gen_id`'s generation, then generates a fresh
|
||||
/// schedule from now (inheriting the rolled-back generation as the base for
|
||||
/// recycle-policy history).
|
||||
pub(super) async fn rollback_schedule(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(user): CurrentUser,
|
||||
Path((channel_id, gen_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
let channel = state.channel_service.find_by_id(channel_id).await?;
|
||||
require_owner(&channel, user.id)?;
|
||||
|
||||
let target = state
|
||||
.schedule_engine
|
||||
.get_schedule_by_id(channel_id, gen_id)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Schedule {} not found", gen_id)))?;
|
||||
|
||||
state
|
||||
.schedule_engine
|
||||
.delete_schedules_after(channel_id, target.generation)
|
||||
.await?;
|
||||
|
||||
let schedule = state
|
||||
.schedule_engine
|
||||
.generate_schedule(channel_id, Utc::now())
|
||||
.await?;
|
||||
|
||||
let _ = state.event_tx.send(domain::DomainEvent::ScheduleGenerated {
|
||||
channel_id,
|
||||
schedule: schedule.clone(),
|
||||
});
|
||||
let detail = format!("rollback to gen {}; {} slots", target.generation, schedule.slots.len());
|
||||
let _ = state.activity_log_repo.log("schedule_rollback", &detail, Some(channel_id)).await;
|
||||
|
||||
Ok((StatusCode::CREATED, Json(ScheduleResponse::from(schedule))))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user