remove OIDC/Postgres, replace ApiError w/ AppError, move params to api-types
This commit is contained in:
@@ -1,144 +1,47 @@
|
||||
use axum::{
|
||||
Json,
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
|
||||
use axum::Json;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use domain::DomainError;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ApiError {
|
||||
#[error("{0}")]
|
||||
Domain(#[from] DomainError),
|
||||
pub struct AppError(pub DomainError);
|
||||
|
||||
#[error("Validation error: {0}")]
|
||||
Validation(String),
|
||||
|
||||
#[error("Internal server error")]
|
||||
Internal(String),
|
||||
|
||||
#[error("Forbidden: {0}")]
|
||||
Forbidden(String),
|
||||
|
||||
#[error("Unauthorized: {0}")]
|
||||
Unauthorized(String),
|
||||
|
||||
#[error("Not found: {0}")]
|
||||
NotFound(String),
|
||||
|
||||
#[error("Conflict: {0}")]
|
||||
Conflict(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ErrorResponse {
|
||||
pub error: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub details: Option<String>,
|
||||
}
|
||||
|
||||
impl IntoResponse for ApiError {
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
let (status, error_response) = match &self {
|
||||
ApiError::Domain(domain_error) => {
|
||||
let status = match domain_error {
|
||||
DomainError::UserNotFound(_)
|
||||
| DomainError::ChannelNotFound(_)
|
||||
| DomainError::NoActiveSchedule(_) => StatusCode::NOT_FOUND,
|
||||
let status = match &self.0 {
|
||||
DomainError::UserNotFound(_)
|
||||
| DomainError::ChannelNotFound(_)
|
||||
| DomainError::NoActiveSchedule(_)
|
||||
| DomainError::NotFound(_) => StatusCode::NOT_FOUND,
|
||||
|
||||
DomainError::UserAlreadyExists(_) => StatusCode::CONFLICT,
|
||||
DomainError::UserAlreadyExists(_) | DomainError::Conflict(_) => StatusCode::CONFLICT,
|
||||
|
||||
DomainError::ValidationError(_) | DomainError::TimezoneError(_) => {
|
||||
StatusCode::BAD_REQUEST
|
||||
}
|
||||
|
||||
DomainError::Unauthenticated(_) => StatusCode::UNAUTHORIZED,
|
||||
DomainError::Forbidden(_) => StatusCode::FORBIDDEN,
|
||||
|
||||
DomainError::RepositoryError(_) | DomainError::InfrastructureError(_) => {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
|
||||
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
};
|
||||
|
||||
(
|
||||
status,
|
||||
ErrorResponse {
|
||||
error: domain_error.to_string(),
|
||||
details: None,
|
||||
},
|
||||
)
|
||||
DomainError::ValidationError(_) | DomainError::TimezoneError(_) => {
|
||||
StatusCode::BAD_REQUEST
|
||||
}
|
||||
|
||||
ApiError::Validation(msg) => (
|
||||
StatusCode::BAD_REQUEST,
|
||||
ErrorResponse {
|
||||
error: "Validation error".to_string(),
|
||||
details: Some(msg.clone()),
|
||||
},
|
||||
),
|
||||
DomainError::Unauthenticated(_) => StatusCode::UNAUTHORIZED,
|
||||
DomainError::Forbidden(_) => StatusCode::FORBIDDEN,
|
||||
|
||||
ApiError::Internal(msg) => {
|
||||
tracing::error!("Internal error: {}", msg);
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
ErrorResponse {
|
||||
error: "Internal server error".to_string(),
|
||||
details: None,
|
||||
},
|
||||
)
|
||||
DomainError::RepositoryError(_) | DomainError::InfrastructureError(_) => {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
|
||||
ApiError::Forbidden(msg) => (
|
||||
StatusCode::FORBIDDEN,
|
||||
ErrorResponse {
|
||||
error: "Forbidden".to_string(),
|
||||
details: Some(msg.clone()),
|
||||
},
|
||||
),
|
||||
|
||||
ApiError::Unauthorized(msg) => (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
ErrorResponse {
|
||||
error: "Unauthorized".to_string(),
|
||||
details: Some(msg.clone()),
|
||||
},
|
||||
),
|
||||
|
||||
ApiError::NotFound(msg) => (
|
||||
StatusCode::NOT_FOUND,
|
||||
ErrorResponse {
|
||||
error: "Not found".to_string(),
|
||||
details: Some(msg.clone()),
|
||||
},
|
||||
),
|
||||
|
||||
ApiError::Conflict(msg) => (
|
||||
StatusCode::CONFLICT,
|
||||
ErrorResponse {
|
||||
error: "Conflict".to_string(),
|
||||
details: Some(msg.clone()),
|
||||
},
|
||||
),
|
||||
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
};
|
||||
|
||||
(status, Json(error_response)).into_response()
|
||||
let body = api_types::ErrorResponse::new(self.0.to_string());
|
||||
(status, Json(body)).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiError {
|
||||
pub fn validation(msg: impl Into<String>) -> Self {
|
||||
Self::Validation(msg.into())
|
||||
}
|
||||
|
||||
pub fn not_found(msg: impl Into<String>) -> Self {
|
||||
Self::NotFound(msg.into())
|
||||
}
|
||||
|
||||
pub fn conflict(msg: impl Into<String>) -> Self {
|
||||
Self::Conflict(msg.into())
|
||||
impl From<DomainError> for AppError {
|
||||
fn from(e: DomainError) -> Self {
|
||||
Self(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for AppError {
|
||||
fn from(e: serde_json::Error) -> Self {
|
||||
Self(DomainError::ValidationError(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use axum::extract::FromRequestParts;
|
||||
use axum::http::request::Parts;
|
||||
use domain::User;
|
||||
use domain::{DomainError, User};
|
||||
|
||||
use crate::errors::ApiError;
|
||||
use crate::errors::AppError;
|
||||
use crate::state::AppState;
|
||||
|
||||
pub struct CurrentUser(pub User);
|
||||
|
||||
impl FromRequestParts<AppState> for CurrentUser {
|
||||
type Rejection = ApiError;
|
||||
type Rejection = AppError;
|
||||
|
||||
async fn from_request_parts(
|
||||
parts: &mut Parts,
|
||||
@@ -25,9 +25,9 @@ impl FromRequestParts<AppState> for CurrentUser {
|
||||
#[cfg(not(feature = "auth-jwt"))]
|
||||
{
|
||||
let _ = (parts, state);
|
||||
Err(ApiError::Unauthorized(
|
||||
Err(AppError(DomainError::Unauthenticated(
|
||||
"No authentication backend configured".to_string(),
|
||||
))
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ impl FromRequestParts<AppState> for CurrentUser {
|
||||
pub struct OptionalCurrentUser(pub Option<User>);
|
||||
|
||||
impl FromRequestParts<AppState> for OptionalCurrentUser {
|
||||
type Rejection = ApiError;
|
||||
type Rejection = AppError;
|
||||
|
||||
async fn from_request_parts(
|
||||
parts: &mut Parts,
|
||||
@@ -69,7 +69,7 @@ impl FromRequestParts<AppState> for OptionalCurrentUser {
|
||||
pub struct AdminUser(pub User);
|
||||
|
||||
impl FromRequestParts<AppState> for AdminUser {
|
||||
type Rejection = ApiError;
|
||||
type Rejection = AppError;
|
||||
|
||||
async fn from_request_parts(
|
||||
parts: &mut Parts,
|
||||
@@ -77,64 +77,64 @@ impl FromRequestParts<AppState> for AdminUser {
|
||||
) -> Result<Self, Self::Rejection> {
|
||||
let CurrentUser(user) = CurrentUser::from_request_parts(parts, state).await?;
|
||||
if !user.is_admin() {
|
||||
return Err(ApiError::Forbidden("Admin access required".to_string()));
|
||||
return Err(AppError(DomainError::Forbidden("Admin access required".to_string())));
|
||||
}
|
||||
Ok(AdminUser(user))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "auth-jwt")]
|
||||
async fn try_jwt_auth(parts: &mut Parts, state: &AppState) -> Result<User, ApiError> {
|
||||
async fn try_jwt_auth(parts: &mut Parts, state: &AppState) -> Result<User, AppError> {
|
||||
use axum::http::header::AUTHORIZATION;
|
||||
|
||||
let auth_header = parts
|
||||
.headers
|
||||
.get(AUTHORIZATION)
|
||||
.ok_or_else(|| ApiError::Unauthorized("Missing Authorization header".to_string()))?;
|
||||
.ok_or_else(|| AppError(DomainError::Unauthenticated("Missing Authorization header".to_string())))?;
|
||||
|
||||
let auth_str = auth_header
|
||||
.to_str()
|
||||
.map_err(|_| ApiError::Unauthorized("Invalid Authorization header encoding".to_string()))?;
|
||||
.map_err(|_| AppError(DomainError::Unauthenticated("Invalid Authorization header encoding".to_string())))?;
|
||||
|
||||
let token = auth_str.strip_prefix("Bearer ").ok_or_else(|| {
|
||||
ApiError::Unauthorized("Authorization header must use Bearer scheme".to_string())
|
||||
AppError(DomainError::Unauthenticated("Authorization header must use Bearer scheme".to_string()))
|
||||
})?;
|
||||
|
||||
validate_jwt_token(token, state).await
|
||||
}
|
||||
|
||||
#[cfg(feature = "auth-jwt")]
|
||||
pub(crate) async fn validate_jwt_token(token: &str, state: &AppState) -> Result<User, ApiError> {
|
||||
pub(crate) async fn validate_jwt_token(token: &str, state: &AppState) -> Result<User, AppError> {
|
||||
let validator = state
|
||||
.jwt_validator
|
||||
.as_ref()
|
||||
.ok_or_else(|| ApiError::Internal("JWT validator not configured".to_string()))?;
|
||||
.ok_or_else(|| AppError(DomainError::InfrastructureError("JWT validator not configured".to_string())))?;
|
||||
|
||||
let claims = validator.validate_access_token(token).map_err(|e| {
|
||||
tracing::debug!("JWT validation failed: {:?}", e);
|
||||
match e {
|
||||
adapter_auth::JwtError::Expired => {
|
||||
ApiError::Unauthorized("Token expired".to_string())
|
||||
AppError(DomainError::Unauthenticated("Token expired".to_string()))
|
||||
}
|
||||
adapter_auth::JwtError::InvalidFormat => {
|
||||
ApiError::Unauthorized("Invalid token format".to_string())
|
||||
AppError(DomainError::Unauthenticated("Invalid token format".to_string()))
|
||||
}
|
||||
_ => ApiError::Unauthorized("Token validation failed".to_string()),
|
||||
_ => AppError(DomainError::Unauthenticated("Token validation failed".to_string())),
|
||||
}
|
||||
})?;
|
||||
|
||||
let user_id: uuid::Uuid = claims
|
||||
.sub
|
||||
.parse()
|
||||
.map_err(|_| ApiError::Unauthorized("Invalid user ID in token".to_string()))?;
|
||||
.map_err(|_| AppError(DomainError::Unauthenticated("Invalid user ID in token".to_string())))?;
|
||||
|
||||
let user = state
|
||||
.auth_deps
|
||||
.user_query
|
||||
.find_by_id(domain::UserId::from(user_id))
|
||||
.await
|
||||
.map_err(|e| ApiError::Internal(format!("Failed to fetch user: {}", e)))?
|
||||
.ok_or_else(|| ApiError::Unauthorized("User not found".to_string()))?;
|
||||
.map_err(|e| AppError(DomainError::InfrastructureError(format!("Failed to fetch user: {}", e))))?
|
||||
.ok_or_else(|| AppError(DomainError::Unauthenticated("User not found".to_string())))?;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
@@ -192,25 +192,6 @@ fn wire_repositories(pool: &DbPool) -> anyhow::Result<WireOutput> {
|
||||
provider_config_query: w.provider_config_query,
|
||||
})
|
||||
}
|
||||
#[cfg(feature = "postgres")]
|
||||
DbPool::Postgres(pg_pool) => {
|
||||
let w = adapter_postgres::wire(pg_pool.clone());
|
||||
Ok(WireOutput {
|
||||
user_command: w.user_command,
|
||||
user_query: w.user_query,
|
||||
channel_command: w.channel_command,
|
||||
channel_query: w.channel_query,
|
||||
schedule_command: w.schedule_command,
|
||||
schedule_query: w.schedule_query,
|
||||
library_command: w.library_command,
|
||||
library_query: w.library_query,
|
||||
activity_query: w.activity_query,
|
||||
settings: w.settings,
|
||||
provider_config_command: w.provider_config_command,
|
||||
provider_config_query: w.provider_config_query,
|
||||
})
|
||||
}
|
||||
_ => anyhow::bail!("database backend not compiled into this binary"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use axum::Json;
|
||||
use axum::extract::{Query, State};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use api_types::{ActivityEventResponse, SettingsResponse};
|
||||
use api_types::{ActivityEventResponse, ActivityLogParams, SettingsResponse};
|
||||
use application::admin::{GetActivityLogQuery, GetSettingsQuery, UpdateSettingsCommand};
|
||||
|
||||
use crate::errors::ApiError;
|
||||
use crate::errors::AppError;
|
||||
use crate::extractors::AdminUser;
|
||||
use crate::state::AppState;
|
||||
|
||||
@@ -15,7 +14,7 @@ const DEFAULT_ACTIVITY_LIMIT: u32 = 50;
|
||||
pub async fn get_settings(
|
||||
State(state): State<AppState>,
|
||||
AdminUser(_user): AdminUser,
|
||||
) -> Result<Json<SettingsResponse>, ApiError> {
|
||||
) -> Result<Json<SettingsResponse>, AppError> {
|
||||
let pairs =
|
||||
application::admin::get_settings::execute(&state.admin_deps, GetSettingsQuery).await?;
|
||||
let settings: HashMap<String, String> = pairs.into_iter().collect();
|
||||
@@ -26,7 +25,7 @@ pub async fn update_settings(
|
||||
State(state): State<AppState>,
|
||||
AdminUser(_user): AdminUser,
|
||||
Json(body): Json<HashMap<String, String>>,
|
||||
) -> Result<Json<SettingsResponse>, ApiError> {
|
||||
) -> Result<Json<SettingsResponse>, AppError> {
|
||||
let settings_vec: Vec<(String, String)> = body.into_iter().collect();
|
||||
let cmd = UpdateSettingsCommand {
|
||||
settings: settings_vec,
|
||||
@@ -39,16 +38,11 @@ pub async fn update_settings(
|
||||
Ok(Json(SettingsResponse { settings }))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ActivityLogParams {
|
||||
pub limit: Option<u32>,
|
||||
}
|
||||
|
||||
pub async fn get_activity_log(
|
||||
State(state): State<AppState>,
|
||||
AdminUser(_user): AdminUser,
|
||||
Query(params): Query<ActivityLogParams>,
|
||||
) -> Result<Json<Vec<ActivityEventResponse>>, ApiError> {
|
||||
) -> Result<Json<Vec<ActivityEventResponse>>, AppError> {
|
||||
let query = GetActivityLogQuery {
|
||||
limit: params.limit.unwrap_or(DEFAULT_ACTIVITY_LIMIT),
|
||||
};
|
||||
|
||||
@@ -3,8 +3,9 @@ use axum::extract::State;
|
||||
|
||||
use api_types::{LoginRequest, RefreshRequest, RegisterRequest, TokenResponse, UserResponse};
|
||||
use application::auth::{LoginCommand, RegisterCommand};
|
||||
use domain::DomainError;
|
||||
|
||||
use crate::errors::ApiError;
|
||||
use crate::errors::AppError;
|
||||
use crate::extractors::CurrentUser;
|
||||
use crate::state::AppState;
|
||||
|
||||
@@ -14,7 +15,7 @@ const SECS_PER_HOUR: u64 = 3600;
|
||||
pub async fn register(
|
||||
State(state): State<AppState>,
|
||||
Json(req): Json<RegisterRequest>,
|
||||
) -> Result<Json<UserResponse>, ApiError> {
|
||||
) -> Result<Json<UserResponse>, AppError> {
|
||||
let cmd = RegisterCommand {
|
||||
email: req.email,
|
||||
password: req.password,
|
||||
@@ -26,7 +27,7 @@ pub async fn register(
|
||||
pub async fn login(
|
||||
State(state): State<AppState>,
|
||||
Json(req): Json<LoginRequest>,
|
||||
) -> Result<Json<TokenResponse>, ApiError> {
|
||||
) -> Result<Json<TokenResponse>, AppError> {
|
||||
let cmd = LoginCommand {
|
||||
email: req.email,
|
||||
password: req.password,
|
||||
@@ -41,13 +42,13 @@ pub async fn login(
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn logout() -> Result<Json<serde_json::Value>, ApiError> {
|
||||
pub async fn logout() -> Result<Json<serde_json::Value>, AppError> {
|
||||
Ok(Json(serde_json::json!({"message": "logged out"})))
|
||||
}
|
||||
|
||||
pub async fn me(
|
||||
CurrentUser(user): CurrentUser,
|
||||
) -> Result<Json<UserResponse>, ApiError> {
|
||||
) -> Result<Json<UserResponse>, AppError> {
|
||||
Ok(Json(UserResponse::from(user)))
|
||||
}
|
||||
|
||||
@@ -55,7 +56,7 @@ pub async fn me(
|
||||
pub async fn get_token(
|
||||
State(state): State<AppState>,
|
||||
Json(req): Json<LoginRequest>,
|
||||
) -> Result<Json<TokenResponse>, ApiError> {
|
||||
) -> Result<Json<TokenResponse>, AppError> {
|
||||
let cmd = LoginCommand {
|
||||
email: req.email,
|
||||
password: req.password,
|
||||
@@ -74,29 +75,29 @@ pub async fn get_token(
|
||||
pub async fn refresh_token(
|
||||
State(state): State<AppState>,
|
||||
Json(req): Json<RefreshRequest>,
|
||||
) -> Result<Json<TokenResponse>, ApiError> {
|
||||
) -> Result<Json<TokenResponse>, AppError> {
|
||||
let validator = state
|
||||
.jwt_validator
|
||||
.as_ref()
|
||||
.ok_or_else(|| ApiError::Internal("JWT not configured".to_string()))?;
|
||||
.ok_or_else(|| AppError(DomainError::InfrastructureError("JWT not configured".to_string())))?;
|
||||
|
||||
let claims = validator.validate_refresh_token(&req.refresh_token).map_err(|e| {
|
||||
tracing::debug!("Refresh token validation failed: {:?}", e);
|
||||
ApiError::Unauthorized("Invalid refresh token".to_string())
|
||||
AppError(DomainError::Unauthenticated("Invalid refresh token".to_string()))
|
||||
})?;
|
||||
|
||||
let user_id: uuid::Uuid = claims
|
||||
.sub
|
||||
.parse()
|
||||
.map_err(|_| ApiError::Unauthorized("Invalid user ID in token".to_string()))?;
|
||||
.map_err(|_| AppError(DomainError::Unauthenticated("Invalid user ID in token".to_string())))?;
|
||||
|
||||
let user = state
|
||||
.auth_deps
|
||||
.user_query
|
||||
.find_by_id(domain::UserId::from(user_id))
|
||||
.await
|
||||
.map_err(|e| ApiError::Internal(format!("Failed to fetch user: {}", e)))?
|
||||
.ok_or_else(|| ApiError::Unauthorized("User not found".to_string()))?;
|
||||
.map_err(|e| AppError(DomainError::InfrastructureError(format!("Failed to fetch user: {}", e))))?
|
||||
.ok_or_else(|| AppError(DomainError::Unauthenticated("User not found".to_string())))?;
|
||||
|
||||
let (access_token, refresh_token) = create_tokens(&user, &state, true)?;
|
||||
Ok(Json(TokenResponse {
|
||||
@@ -111,23 +112,23 @@ fn create_tokens(
|
||||
user: &domain::User,
|
||||
state: &AppState,
|
||||
remember_me: bool,
|
||||
) -> Result<(String, Option<String>), ApiError> {
|
||||
) -> Result<(String, Option<String>), AppError> {
|
||||
#[cfg(feature = "auth-jwt")]
|
||||
{
|
||||
let validator = state
|
||||
.jwt_validator
|
||||
.as_ref()
|
||||
.ok_or_else(|| ApiError::Internal("JWT not configured".to_string()))?;
|
||||
.ok_or_else(|| AppError(DomainError::InfrastructureError("JWT not configured".to_string())))?;
|
||||
|
||||
let access = validator
|
||||
.create_token(user)
|
||||
.map_err(|e| ApiError::Internal(format!("Failed to create token: {}", e)))?;
|
||||
.map_err(|e| AppError(DomainError::InfrastructureError(format!("Failed to create token: {}", e))))?;
|
||||
|
||||
let refresh = if remember_me {
|
||||
Some(
|
||||
validator
|
||||
.create_refresh_token(user)
|
||||
.map_err(|e| ApiError::Internal(format!("Failed to create refresh token: {}", e)))?,
|
||||
.map_err(|e| AppError(DomainError::InfrastructureError(format!("Failed to create refresh token: {}", e))))?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@@ -139,6 +140,6 @@ fn create_tokens(
|
||||
#[cfg(not(feature = "auth-jwt"))]
|
||||
{
|
||||
let _ = (user, state, remember_me);
|
||||
Err(ApiError::Internal("JWT feature not enabled".to_string()))
|
||||
Err(AppError(DomainError::InfrastructureError("JWT feature not enabled".to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,16 @@ use application::config_snapshots::{
|
||||
GetSnapshotQuery, ListSnapshotsQuery, PatchLabelCommand, RestoreSnapshotCommand,
|
||||
SaveSnapshotCommand,
|
||||
};
|
||||
use domain::DomainError;
|
||||
|
||||
use crate::errors::ApiError;
|
||||
use crate::errors::AppError;
|
||||
use crate::extractors::CurrentUser;
|
||||
use crate::state::AppState;
|
||||
|
||||
pub async fn list_channels(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
) -> Result<Json<Vec<ChannelResponse>>, ApiError> {
|
||||
) -> Result<Json<Vec<ChannelResponse>>, AppError> {
|
||||
let channels =
|
||||
application::channels::list::execute(&state.channel_query_deps, ListChannelsQuery).await?;
|
||||
Ok(Json(channels.into_iter().map(ChannelResponse::from).collect()))
|
||||
@@ -30,7 +31,7 @@ pub async fn list_channels(
|
||||
pub async fn list_my_channels(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(user): CurrentUser,
|
||||
) -> Result<Json<Vec<ChannelResponse>>, ApiError> {
|
||||
) -> Result<Json<Vec<ChannelResponse>>, AppError> {
|
||||
let query = ListByOwnerQuery {
|
||||
owner_id: user.id(),
|
||||
};
|
||||
@@ -43,7 +44,7 @@ pub async fn create_channel(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(user): CurrentUser,
|
||||
Json(req): Json<CreateChannelRequest>,
|
||||
) -> Result<Json<ChannelResponse>, ApiError> {
|
||||
) -> Result<Json<ChannelResponse>, AppError> {
|
||||
let cmd = CreateChannelCommand {
|
||||
owner_id: user.id(),
|
||||
name: req.name,
|
||||
@@ -57,13 +58,13 @@ pub async fn get_channel(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
) -> Result<Json<ChannelResponse>, ApiError> {
|
||||
) -> Result<Json<ChannelResponse>, AppError> {
|
||||
let query = GetChannelQuery {
|
||||
channel_id: id.into(),
|
||||
};
|
||||
let channel = application::channels::get::execute(&state.channel_query_deps, query)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::not_found(format!("Channel {id} not found")))?;
|
||||
.ok_or_else(|| AppError(DomainError::NotFound(format!("Channel {id} not found"))))?;
|
||||
Ok(Json(ChannelResponse::from(channel)))
|
||||
}
|
||||
|
||||
@@ -72,12 +73,12 @@ pub async fn update_channel(
|
||||
CurrentUser(user): CurrentUser,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
Json(req): Json<UpdateChannelRequest>,
|
||||
) -> Result<Json<ChannelResponse>, ApiError> {
|
||||
) -> Result<Json<ChannelResponse>, AppError> {
|
||||
let schedule_config = req
|
||||
.schedule_config
|
||||
.map(|v| {
|
||||
serde_json::from_value(v)
|
||||
.map_err(|e| ApiError::validation(format!("Invalid schedule_config: {e}")))
|
||||
.map_err(|e| AppError(DomainError::ValidationError(format!("Invalid schedule_config: {e}"))))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
@@ -85,7 +86,7 @@ pub async fn update_channel(
|
||||
.recycle_policy
|
||||
.map(|v| {
|
||||
serde_json::from_value(v)
|
||||
.map_err(|e| ApiError::validation(format!("Invalid recycle_policy: {e}")))
|
||||
.map_err(|e| AppError(DomainError::ValidationError(format!("Invalid recycle_policy: {e}"))))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
@@ -107,7 +108,7 @@ pub async fn delete_channel(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(user): CurrentUser,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
) -> Result<axum::http::StatusCode, ApiError> {
|
||||
) -> Result<axum::http::StatusCode, AppError> {
|
||||
let cmd = DeleteChannelCommand {
|
||||
channel_id: id.into(),
|
||||
owner_id: user.id(),
|
||||
@@ -120,7 +121,7 @@ pub async fn save_snapshot(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
) -> Result<Json<ConfigSnapshotResponse>, ApiError> {
|
||||
) -> Result<Json<ConfigSnapshotResponse>, AppError> {
|
||||
let cmd = SaveSnapshotCommand {
|
||||
channel_id: id.into(),
|
||||
label: None,
|
||||
@@ -133,7 +134,7 @@ pub async fn list_snapshots(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
) -> Result<Json<Vec<ConfigSnapshotResponse>>, ApiError> {
|
||||
) -> Result<Json<Vec<ConfigSnapshotResponse>>, AppError> {
|
||||
let query = ListSnapshotsQuery {
|
||||
channel_id: id.into(),
|
||||
};
|
||||
@@ -145,14 +146,14 @@ 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> {
|
||||
) -> Result<Json<ConfigSnapshotResponse>, AppError> {
|
||||
let query = GetSnapshotQuery {
|
||||
channel_id: id.into(),
|
||||
snapshot_id: snapshot_id.into(),
|
||||
};
|
||||
let snap = application::config_snapshots::get::execute(&state.config_snapshot_deps, query)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::not_found("Snapshot not found"))?;
|
||||
.ok_or_else(|| AppError(DomainError::NotFound("Snapshot not found".into())))?;
|
||||
Ok(Json(ConfigSnapshotResponse::from(snap)))
|
||||
}
|
||||
|
||||
@@ -161,7 +162,7 @@ pub async fn patch_snapshot(
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Path((id, snapshot_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
||||
Json(req): Json<PatchSnapshotRequest>,
|
||||
) -> Result<Json<ConfigSnapshotResponse>, ApiError> {
|
||||
) -> Result<Json<ConfigSnapshotResponse>, AppError> {
|
||||
let cmd = PatchLabelCommand {
|
||||
channel_id: id.into(),
|
||||
snapshot_id: snapshot_id.into(),
|
||||
@@ -170,7 +171,7 @@ pub async fn patch_snapshot(
|
||||
let snap =
|
||||
application::config_snapshots::patch_label::execute(&state.config_snapshot_deps, cmd)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::not_found("Snapshot not found"))?;
|
||||
.ok_or_else(|| AppError(DomainError::NotFound("Snapshot not found".into())))?;
|
||||
Ok(Json(ConfigSnapshotResponse::from(snap)))
|
||||
}
|
||||
|
||||
@@ -178,7 +179,7 @@ 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> {
|
||||
) -> Result<Json<ChannelResponse>, AppError> {
|
||||
let cmd = RestoreSnapshotCommand {
|
||||
channel_id: id.into(),
|
||||
snapshot_id: snapshot_id.into(),
|
||||
|
||||
@@ -3,14 +3,14 @@ use axum::extract::State;
|
||||
|
||||
use api_types::{ConfigResponse, ProviderCapabilitiesResponse, ProviderInfo};
|
||||
|
||||
use crate::errors::ApiError;
|
||||
use crate::errors::AppError;
|
||||
use crate::state::AppState;
|
||||
|
||||
const FALLBACK_STREAMING_PROTOCOL: &str = "direct_file";
|
||||
|
||||
pub async fn get_config(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<ConfigResponse>, ApiError> {
|
||||
) -> Result<Json<ConfigResponse>, AppError> {
|
||||
let registry = &state.provider_registry;
|
||||
let provider_ids = registry.provider_ids();
|
||||
let primary_id = registry.primary_id().to_string();
|
||||
|
||||
@@ -2,22 +2,24 @@ use axum::extract::{Path, State};
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::IntoResponse;
|
||||
|
||||
use crate::errors::ApiError;
|
||||
use domain::DomainError;
|
||||
|
||||
use crate::errors::AppError;
|
||||
use crate::state::AppState;
|
||||
|
||||
pub async fn stream_file(
|
||||
State(_state): State<AppState>,
|
||||
Path(_id): Path<String>,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
Err::<StatusCode, _>(ApiError::NotFound(
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
Err::<StatusCode, _>(AppError(DomainError::NotFound(
|
||||
"Local file streaming not yet wired in presentation crate".to_string(),
|
||||
))
|
||||
)))
|
||||
}
|
||||
|
||||
pub async fn rescan(
|
||||
State(_state): State<AppState>,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
Err::<StatusCode, _>(ApiError::NotFound(
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
Err::<StatusCode, _>(AppError(DomainError::NotFound(
|
||||
"Local file rescan not yet wired in presentation crate".to_string(),
|
||||
))
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -1,27 +1,22 @@
|
||||
use axum::extract::{Query, State};
|
||||
use axum::http::header;
|
||||
use axum::response::IntoResponse;
|
||||
use serde::Deserialize;
|
||||
|
||||
use api_types::IptvParams;
|
||||
use application::iptv::{GetM3uQuery, GetXmltvQuery};
|
||||
|
||||
use crate::errors::ApiError;
|
||||
use crate::errors::AppError;
|
||||
use crate::extractors::OptionalCurrentUser;
|
||||
use crate::state::AppState;
|
||||
|
||||
const M3U_CONTENT_TYPE: &str = "audio/x-mpegurl; charset=utf-8";
|
||||
const XML_CONTENT_TYPE: &str = "application/xml; charset=utf-8";
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct IptvParams {
|
||||
pub token: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn m3u_playlist(
|
||||
State(state): State<AppState>,
|
||||
OptionalCurrentUser(_user): OptionalCurrentUser,
|
||||
Query(params): Query<IptvParams>,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let query = GetM3uQuery {
|
||||
base_url: state.config.base_url.clone(),
|
||||
token: params.token,
|
||||
@@ -33,7 +28,7 @@ pub async fn m3u_playlist(
|
||||
pub async fn xmltv_epg(
|
||||
State(state): State<AppState>,
|
||||
OptionalCurrentUser(_user): OptionalCurrentUser,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let content = application::iptv::xmltv::execute(&state.iptv_deps, GetXmltvQuery).await?;
|
||||
Ok(([(header::CONTENT_TYPE, XML_CONTENT_TYPE)], content))
|
||||
}
|
||||
|
||||
@@ -1,40 +1,28 @@
|
||||
use axum::Json;
|
||||
use axum::extract::{Path, Query, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Serialize;
|
||||
|
||||
use api_types::{CollectionResponse, LibraryItemResponse, PaginatedResponse, SeasonResponse, ShowResponse};
|
||||
use api_types::{
|
||||
CollectionResponse, GenresParams, LibraryItemResponse, LibrarySearchParams, PaginatedResponse,
|
||||
ProviderParam, SeasonResponse, SeasonsParams, ShowResponse, ShowsParams,
|
||||
};
|
||||
use application::library::{
|
||||
GetItemQuery, GetSyncStatusQuery, ListCollectionsQuery, ListGenresQuery, ListSeasonsQuery,
|
||||
ListShowsQuery, SearchItemsQuery, TriggerSyncCommand,
|
||||
};
|
||||
use domain::DomainError;
|
||||
|
||||
use crate::errors::ApiError;
|
||||
use crate::errors::AppError;
|
||||
use crate::extractors::{AdminUser, CurrentUser};
|
||||
use crate::state::AppState;
|
||||
|
||||
const DEFAULT_SEARCH_LIMIT: u32 = 50;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct LibrarySearchParams {
|
||||
pub provider: Option<String>,
|
||||
pub content_type: Option<String>,
|
||||
#[serde(default, rename = "genres[]")]
|
||||
pub genres: Vec<String>,
|
||||
pub search_term: Option<String>,
|
||||
pub collection_id: Option<String>,
|
||||
#[serde(default, rename = "series_names[]")]
|
||||
pub series_names: Vec<String>,
|
||||
pub season_number: Option<u32>,
|
||||
pub decade: Option<u16>,
|
||||
pub offset: Option<u32>,
|
||||
pub limit: Option<u32>,
|
||||
}
|
||||
|
||||
pub async fn search_items(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Query(params): Query<LibrarySearchParams>,
|
||||
) -> Result<Json<PaginatedResponse<LibraryItemResponse>>, ApiError> {
|
||||
) -> Result<Json<PaginatedResponse<LibraryItemResponse>>, AppError> {
|
||||
let query = SearchItemsQuery {
|
||||
provider_id: params.provider,
|
||||
content_type: params.content_type,
|
||||
@@ -58,11 +46,11 @@ pub async fn get_item(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<Json<LibraryItemResponse>, ApiError> {
|
||||
) -> Result<Json<LibraryItemResponse>, AppError> {
|
||||
let query = GetItemQuery { item_id: id.clone() };
|
||||
let item = application::library::get_item::execute(&state.library_query_deps, query)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::not_found(format!("Library item {id} not found")))?;
|
||||
.ok_or_else(|| AppError(DomainError::NotFound(format!("Library item {id} not found"))))?;
|
||||
Ok(Json(LibraryItemResponse::from(item)))
|
||||
}
|
||||
|
||||
@@ -70,7 +58,7 @@ pub async fn list_collections(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Query(params): Query<ProviderParam>,
|
||||
) -> Result<Json<Vec<CollectionResponse>>, ApiError> {
|
||||
) -> Result<Json<Vec<CollectionResponse>>, AppError> {
|
||||
let query = ListCollectionsQuery {
|
||||
provider_id: params.provider,
|
||||
};
|
||||
@@ -84,24 +72,11 @@ pub async fn list_collections(
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ProviderParam {
|
||||
pub provider: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ShowsParams {
|
||||
pub provider: Option<String>,
|
||||
pub search_term: Option<String>,
|
||||
#[serde(default, rename = "genres[]")]
|
||||
pub genres: Vec<String>,
|
||||
}
|
||||
|
||||
pub async fn list_shows(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Query(params): Query<ShowsParams>,
|
||||
) -> Result<Json<Vec<ShowResponse>>, ApiError> {
|
||||
) -> Result<Json<Vec<ShowResponse>>, AppError> {
|
||||
let query = ListShowsQuery {
|
||||
provider_id: params.provider,
|
||||
search_term: params.search_term,
|
||||
@@ -111,17 +86,11 @@ pub async fn list_shows(
|
||||
Ok(Json(shows.into_iter().map(ShowResponse::from).collect()))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SeasonsParams {
|
||||
pub series_name: String,
|
||||
pub provider: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn list_seasons(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Query(params): Query<SeasonsParams>,
|
||||
) -> Result<Json<Vec<SeasonResponse>>, ApiError> {
|
||||
) -> Result<Json<Vec<SeasonResponse>>, AppError> {
|
||||
let query = ListSeasonsQuery {
|
||||
series_name: params.series_name,
|
||||
provider_id: params.provider,
|
||||
@@ -133,17 +102,11 @@ pub async fn list_seasons(
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct GenresParams {
|
||||
pub content_type: Option<String>,
|
||||
pub provider: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn list_genres(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Query(params): Query<GenresParams>,
|
||||
) -> Result<Json<Vec<String>>, ApiError> {
|
||||
) -> Result<Json<Vec<String>>, AppError> {
|
||||
let query = ListGenresQuery {
|
||||
content_type: params.content_type,
|
||||
provider_id: params.provider,
|
||||
@@ -166,7 +129,7 @@ pub(crate) struct SyncStatusEntry {
|
||||
pub async fn sync_status(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
) -> Result<Json<Vec<SyncStatusEntry>>, ApiError> {
|
||||
) -> Result<Json<Vec<SyncStatusEntry>>, AppError> {
|
||||
let entries =
|
||||
application::library::get_sync_status::execute(&state.library_query_deps, GetSyncStatusQuery)
|
||||
.await?;
|
||||
@@ -187,15 +150,15 @@ pub async fn sync_status(
|
||||
pub async fn trigger_sync(
|
||||
State(state): State<AppState>,
|
||||
AdminUser(_user): AdminUser,
|
||||
) -> Result<axum::http::StatusCode, ApiError> {
|
||||
) -> Result<axum::http::StatusCode, AppError> {
|
||||
let cmd = TriggerSyncCommand { provider_id: None };
|
||||
application::library::sync::execute(&state.library_command_deps, cmd)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
if e.to_string().contains("already running") {
|
||||
ApiError::conflict(e.to_string())
|
||||
AppError(DomainError::Conflict(e.to_string()))
|
||||
} else {
|
||||
ApiError::from(e)
|
||||
AppError::from(e)
|
||||
}
|
||||
})?;
|
||||
|
||||
|
||||
@@ -5,15 +5,16 @@ use api_types::{ProviderConfigRequest, ProviderConfigResponse};
|
||||
use application::providers::{
|
||||
DeleteProviderCommand, GetProviderQuery, ListProvidersQuery, UpsertProviderCommand,
|
||||
};
|
||||
use domain::DomainError;
|
||||
|
||||
use crate::errors::ApiError;
|
||||
use crate::errors::AppError;
|
||||
use crate::extractors::AdminUser;
|
||||
use crate::state::AppState;
|
||||
|
||||
pub async fn list_providers(
|
||||
State(state): State<AppState>,
|
||||
AdminUser(_user): AdminUser,
|
||||
) -> Result<Json<Vec<ProviderConfigResponse>>, ApiError> {
|
||||
) -> Result<Json<Vec<ProviderConfigResponse>>, AppError> {
|
||||
let providers =
|
||||
application::providers::list::execute(&state.provider_deps, ListProvidersQuery).await?;
|
||||
Ok(Json(
|
||||
@@ -28,11 +29,11 @@ pub async fn get_provider(
|
||||
State(state): State<AppState>,
|
||||
AdminUser(_user): AdminUser,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<Json<ProviderConfigResponse>, ApiError> {
|
||||
) -> Result<Json<ProviderConfigResponse>, AppError> {
|
||||
let query = GetProviderQuery { id: id.clone() };
|
||||
let provider = application::providers::get::execute(&state.provider_deps, query)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::not_found(format!("Provider {id} not found")))?;
|
||||
.ok_or_else(|| AppError(DomainError::NotFound(format!("Provider {id} not found"))))?;
|
||||
Ok(Json(ProviderConfigResponse::from(provider)))
|
||||
}
|
||||
|
||||
@@ -41,9 +42,9 @@ pub async fn upsert_provider(
|
||||
AdminUser(_user): AdminUser,
|
||||
Path(id): Path<String>,
|
||||
Json(req): Json<ProviderConfigRequest>,
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
) -> Result<Json<serde_json::Value>, AppError> {
|
||||
let config_json = serde_json::to_string(&req.config)
|
||||
.map_err(|e| ApiError::validation(format!("Invalid config JSON: {e}")))?;
|
||||
.map_err(|e| AppError(DomainError::ValidationError(format!("Invalid config JSON: {e}"))))?;
|
||||
|
||||
let cmd = UpsertProviderCommand {
|
||||
id,
|
||||
@@ -59,7 +60,7 @@ pub async fn delete_provider(
|
||||
State(state): State<AppState>,
|
||||
AdminUser(_user): AdminUser,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<axum::http::StatusCode, ApiError> {
|
||||
) -> Result<axum::http::StatusCode, AppError> {
|
||||
let cmd = DeleteProviderCommand { id };
|
||||
application::providers::delete::execute(&state.provider_deps, cmd).await?;
|
||||
Ok(axum::http::StatusCode::NO_CONTENT)
|
||||
|
||||
@@ -11,7 +11,7 @@ use application::schedule::{
|
||||
GetStreamUrlQuery, ListHistoryQuery,
|
||||
};
|
||||
|
||||
use crate::errors::ApiError;
|
||||
use crate::errors::AppError;
|
||||
use crate::extractors::CurrentUser;
|
||||
use crate::state::AppState;
|
||||
|
||||
@@ -19,7 +19,7 @@ pub async fn generate_schedule(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
) -> Result<Json<ScheduleResponse>, ApiError> {
|
||||
) -> 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)))
|
||||
@@ -29,7 +29,7 @@ pub async fn get_active_schedule(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
) -> Result<axum::response::Response, ApiError> {
|
||||
) -> Result<axum::response::Response, AppError> {
|
||||
let query = GetActiveScheduleQuery { channel_id: id };
|
||||
match application::schedule::get_active::execute(&state.schedule_deps, query).await? {
|
||||
Some(schedule) => Ok(Json(ScheduleResponse::from(schedule)).into_response()),
|
||||
@@ -40,7 +40,7 @@ pub async fn get_active_schedule(
|
||||
pub async fn get_current_broadcast(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
) -> Result<axum::response::Response, ApiError> {
|
||||
) -> Result<axum::response::Response, AppError> {
|
||||
let query = GetCurrentBroadcastQuery { channel_id: id };
|
||||
match application::schedule::get_current_broadcast::execute(&state.schedule_deps, query).await?
|
||||
{
|
||||
@@ -69,7 +69,7 @@ pub async fn get_current_broadcast(
|
||||
pub async fn get_epg(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
) -> Result<Json<Vec<SlotResponse>>, ApiError> {
|
||||
) -> 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()))
|
||||
@@ -78,7 +78,7 @@ pub async fn get_epg(
|
||||
pub async fn get_stream(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
) -> Result<axum::response::Response, ApiError> {
|
||||
) -> Result<axum::response::Response, AppError> {
|
||||
let broadcast_query = GetCurrentBroadcastQuery { channel_id: id };
|
||||
let broadcast =
|
||||
application::schedule::get_current_broadcast::execute(&state.schedule_deps, broadcast_query)
|
||||
@@ -107,7 +107,7 @@ pub async fn list_schedule_history(
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(_user): CurrentUser,
|
||||
Path(id): Path<uuid::Uuid>,
|
||||
) -> Result<Json<Vec<ScheduleHistoryEntry>>, ApiError> {
|
||||
) -> Result<Json<Vec<ScheduleHistoryEntry>>, AppError> {
|
||||
let query = ListHistoryQuery { channel_id: id };
|
||||
let history =
|
||||
application::schedule::list_history::execute(&state.schedule_deps, query).await?;
|
||||
|
||||
Reference in New Issue
Block a user