presentation: HTTP server crate w/ handlers, routes, background tasks
Axum binary that wires all clean-arch crates together: - AppState holds pre-built Deps structs (auth, channels, schedule, library, etc.) - JWT extractors (CurrentUser, AdminUser, OptionalCurrentUser) - Handlers delegate to application use cases, map to api-types DTOs - Routes: auth, channels, schedule, library, admin, providers, config, iptv - Background: auto-scheduler, broadcast poller, webhook consumer, library sync - Factory builds everything from Config + DbPool - SimpleProviderRegistry impl of IProviderRegistry trait - NoopMediaProvider fallback
This commit is contained in:
200
crates/presentation/src/handlers/channels.rs
Normal file
200
crates/presentation/src/handlers/channels.rs
Normal file
@@ -0,0 +1,200 @@
|
||||
//! Channel CRUD handlers.
|
||||
|
||||
use axum::Json;
|
||||
use axum::extract::{Path, State};
|
||||
|
||||
use api_types::{
|
||||
ChannelResponse, ConfigSnapshotResponse, CreateChannelRequest, PatchSnapshotRequest,
|
||||
UpdateChannelRequest,
|
||||
};
|
||||
use application::channels::{
|
||||
CreateChannelCommand, DeleteChannelCommand, GetChannelQuery, ListByOwnerQuery,
|
||||
ListChannelsQuery, UpdateChannelCommand,
|
||||
};
|
||||
use application::config_snapshots::{
|
||||
GetSnapshotQuery, ListSnapshotsQuery, PatchLabelCommand, RestoreSnapshotCommand,
|
||||
SaveSnapshotCommand,
|
||||
};
|
||||
|
||||
use crate::errors::ApiError;
|
||||
use crate::extractors::CurrentUser;
|
||||
use crate::state::AppState;
|
||||
|
||||
/// GET /channels
|
||||
pub async fn list_channels(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
) -> Result<Json<Vec<ChannelResponse>>, ApiError> {
|
||||
let channels =
|
||||
application::channels::list::execute(&state.channel_query_deps, ListChannelsQuery).await?;
|
||||
Ok(Json(channels.into_iter().map(ChannelResponse::from).collect()))
|
||||
}
|
||||
|
||||
/// GET /channels/mine
|
||||
pub async fn list_my_channels(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(user): CurrentUser,
|
||||
) -> Result<Json<Vec<ChannelResponse>>, ApiError> {
|
||||
let query = ListByOwnerQuery {
|
||||
owner_id: user.id().value(),
|
||||
};
|
||||
let channels =
|
||||
application::channels::list_by_owner::execute(&state.channel_query_deps, query).await?;
|
||||
Ok(Json(channels.into_iter().map(ChannelResponse::from).collect()))
|
||||
}
|
||||
|
||||
/// POST /channels
|
||||
pub async fn create_channel(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(user): CurrentUser,
|
||||
Json(req): Json<CreateChannelRequest>,
|
||||
) -> Result<Json<ChannelResponse>, ApiError> {
|
||||
let cmd = CreateChannelCommand {
|
||||
owner_id: user.id().value(),
|
||||
name: req.name,
|
||||
timezone: req.timezone,
|
||||
};
|
||||
let channel = application::channels::create::execute(&state.channel_command_deps, cmd).await?;
|
||||
Ok(Json(ChannelResponse::from(channel)))
|
||||
}
|
||||
|
||||
/// GET /channels/:id
|
||||
pub async fn get_channel(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
) -> Result<Json<ChannelResponse>, ApiError> {
|
||||
let query = GetChannelQuery { channel_id: id };
|
||||
let channel = application::channels::get::execute(&state.channel_query_deps, query)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::not_found(format!("Channel {id} not found")))?;
|
||||
Ok(Json(ChannelResponse::from(channel)))
|
||||
}
|
||||
|
||||
/// PUT /channels/:id
|
||||
pub async fn update_channel(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(user): CurrentUser,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
Json(req): Json<UpdateChannelRequest>,
|
||||
) -> Result<Json<ChannelResponse>, ApiError> {
|
||||
let schedule_config = req
|
||||
.schedule_config
|
||||
.map(|v| {
|
||||
serde_json::from_value(v)
|
||||
.map_err(|e| ApiError::validation(format!("Invalid schedule_config: {e}")))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let recycle_policy = req
|
||||
.recycle_policy
|
||||
.map(|v| {
|
||||
serde_json::from_value(v)
|
||||
.map_err(|e| ApiError::validation(format!("Invalid recycle_policy: {e}")))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let cmd = UpdateChannelCommand {
|
||||
channel_id: id,
|
||||
owner_id: user.id().value(),
|
||||
name: req.name,
|
||||
description: req.description.map(Some),
|
||||
timezone: req.timezone,
|
||||
schedule_config,
|
||||
recycle_policy,
|
||||
auto_schedule: req.auto_schedule,
|
||||
};
|
||||
let channel = application::channels::update::execute(&state.channel_command_deps, cmd).await?;
|
||||
Ok(Json(ChannelResponse::from(channel)))
|
||||
}
|
||||
|
||||
/// DELETE /channels/:id
|
||||
pub async fn delete_channel(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(user): CurrentUser,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
) -> Result<axum::http::StatusCode, ApiError> {
|
||||
let cmd = DeleteChannelCommand {
|
||||
channel_id: id,
|
||||
owner_id: user.id().value(),
|
||||
};
|
||||
application::channels::delete::execute(&state.channel_command_deps, cmd).await?;
|
||||
Ok(axum::http::StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
// ── Config snapshots ─────────────────────────────────────────────────────
|
||||
|
||||
/// POST /channels/:id/snapshots
|
||||
pub async fn save_snapshot(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
) -> Result<Json<ConfigSnapshotResponse>, ApiError> {
|
||||
let cmd = SaveSnapshotCommand {
|
||||
channel_id: id,
|
||||
label: None,
|
||||
};
|
||||
let snap = application::config_snapshots::save::execute(&state.config_snapshot_deps, cmd).await?;
|
||||
Ok(Json(ConfigSnapshotResponse::from(snap)))
|
||||
}
|
||||
|
||||
/// GET /channels/:id/snapshots
|
||||
pub async fn list_snapshots(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
) -> Result<Json<Vec<ConfigSnapshotResponse>>, ApiError> {
|
||||
let query = ListSnapshotsQuery { channel_id: id };
|
||||
let snaps = application::config_snapshots::list::execute(&state.config_snapshot_deps, query).await?;
|
||||
Ok(Json(snaps.into_iter().map(ConfigSnapshotResponse::from).collect()))
|
||||
}
|
||||
|
||||
/// GET /channels/:id/snapshots/:snapshot_id
|
||||
pub async fn get_snapshot(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Path((id, snapshot_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
||||
) -> Result<Json<ConfigSnapshotResponse>, ApiError> {
|
||||
let query = GetSnapshotQuery {
|
||||
channel_id: id,
|
||||
snapshot_id,
|
||||
};
|
||||
let snap = application::config_snapshots::get::execute(&state.config_snapshot_deps, query)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::not_found("Snapshot not found"))?;
|
||||
Ok(Json(ConfigSnapshotResponse::from(snap)))
|
||||
}
|
||||
|
||||
/// PATCH /channels/:id/snapshots/:snapshot_id
|
||||
pub async fn patch_snapshot(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Path((id, snapshot_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
||||
Json(req): Json<PatchSnapshotRequest>,
|
||||
) -> Result<Json<ConfigSnapshotResponse>, ApiError> {
|
||||
let cmd = PatchLabelCommand {
|
||||
channel_id: id,
|
||||
snapshot_id,
|
||||
label: req.label,
|
||||
};
|
||||
let snap =
|
||||
application::config_snapshots::patch_label::execute(&state.config_snapshot_deps, cmd)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::not_found("Snapshot not found"))?;
|
||||
Ok(Json(ConfigSnapshotResponse::from(snap)))
|
||||
}
|
||||
|
||||
/// POST /channels/:id/snapshots/:snapshot_id/restore
|
||||
pub async fn restore_snapshot(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Path((id, snapshot_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
||||
) -> Result<Json<ChannelResponse>, ApiError> {
|
||||
let cmd = RestoreSnapshotCommand {
|
||||
channel_id: id,
|
||||
snapshot_id,
|
||||
};
|
||||
let channel =
|
||||
application::config_snapshots::restore::execute(&state.config_snapshot_deps, cmd).await?;
|
||||
Ok(Json(ChannelResponse::from(channel)))
|
||||
}
|
||||
Reference in New Issue
Block a user