Files
k-tv/k-tv-backend/api/src/state.rs
Gabriel Kaszewski 1102e385f3 feat(transcoding): add FFmpeg HLS transcoding support
- Introduced `TranscodeManager` for managing on-demand transcoding of local video files.
- Added configuration options for transcoding in `Config` and `LocalFilesConfig`.
- Implemented new API routes for managing transcoding settings, stats, and cache.
- Updated `LocalFilesProvider` to support transcoding capabilities.
- Created frontend components for managing transcode settings and displaying stats.
- Added database migration for transcode settings.
- Enhanced existing routes and DTOs to accommodate new transcoding features.
2026-03-15 00:34:23 +01:00

144 lines
5.0 KiB
Rust

//! Application State
//!
//! Holds shared state for the application.
use axum::extract::FromRef;
use axum_extra::extract::cookie::Key;
#[cfg(feature = "auth-jwt")]
use infra::auth::jwt::{JwtConfig, JwtValidator};
#[cfg(feature = "auth-oidc")]
use infra::auth::oidc::OidcService;
use std::sync::Arc;
use crate::config::Config;
use domain::{ChannelService, ScheduleEngineService, UserService};
#[derive(Clone)]
pub struct AppState {
pub user_service: Arc<UserService>,
pub channel_service: Arc<ChannelService>,
pub schedule_engine: Arc<ScheduleEngineService>,
pub provider_registry: Arc<infra::ProviderRegistry>,
pub cookie_key: Key,
#[cfg(feature = "auth-oidc")]
pub oidc_service: Option<Arc<OidcService>>,
#[cfg(feature = "auth-jwt")]
pub jwt_validator: Option<Arc<JwtValidator>>,
pub config: Arc<Config>,
/// Index for the local-files provider, used by the rescan route.
#[cfg(feature = "local-files")]
pub local_index: Option<Arc<infra::LocalIndex>>,
/// TranscodeManager for FFmpeg HLS transcoding (requires TRANSCODE_DIR).
#[cfg(feature = "local-files")]
pub transcode_manager: Option<Arc<infra::TranscodeManager>>,
/// SQLite pool for transcode settings CRUD.
#[cfg(feature = "local-files")]
pub sqlite_pool: Option<sqlx::SqlitePool>,
}
impl AppState {
pub async fn new(
user_service: UserService,
channel_service: ChannelService,
schedule_engine: ScheduleEngineService,
provider_registry: Arc<infra::ProviderRegistry>,
config: Config,
) -> anyhow::Result<Self> {
let cookie_key = Key::derive_from(config.cookie_secret.as_bytes());
#[cfg(feature = "auth-oidc")]
let oidc_service = if let (Some(issuer), Some(id), secret, Some(redirect), resource_id) = (
&config.oidc_issuer,
&config.oidc_client_id,
&config.oidc_client_secret,
&config.oidc_redirect_url,
&config.oidc_resource_id,
) {
tracing::info!("Initializing OIDC service with issuer: {}", issuer);
let issuer_url = domain::IssuerUrl::new(issuer)
.map_err(|e| anyhow::anyhow!("Invalid OIDC issuer URL: {}", e))?;
let client_id = domain::ClientId::new(id)
.map_err(|e| anyhow::anyhow!("Invalid OIDC client ID: {}", e))?;
let client_secret = secret.as_ref().map(|s| domain::ClientSecret::new(s));
let redirect_url = domain::RedirectUrl::new(redirect)
.map_err(|e| anyhow::anyhow!("Invalid OIDC redirect URL: {}", e))?;
let resource = resource_id
.as_ref()
.map(|r| domain::ResourceId::new(r))
.transpose()
.map_err(|e| anyhow::anyhow!("Invalid OIDC resource ID: {}", e))?;
Some(Arc::new(
OidcService::new(issuer_url, client_id, client_secret, redirect_url, resource)
.await?,
))
} else {
None
};
#[cfg(feature = "auth-jwt")]
let jwt_validator = {
let secret = match &config.jwt_secret {
Some(s) if !s.is_empty() => s.clone(),
_ => {
if config.is_production {
anyhow::bail!("JWT_SECRET is required in production");
}
tracing::warn!(
"⚠️ JWT_SECRET not set — using insecure development secret. DO NOT USE IN PRODUCTION!"
);
"k-template-dev-secret-not-for-production-use-only".to_string()
}
};
tracing::info!("Initializing JWT validator");
let jwt_config = JwtConfig::new(
secret,
config.jwt_issuer.clone(),
config.jwt_audience.clone(),
Some(config.jwt_expiry_hours),
config.is_production,
)?;
Some(Arc::new(JwtValidator::new(jwt_config)))
};
Ok(Self {
user_service: Arc::new(user_service),
channel_service: Arc::new(channel_service),
schedule_engine: Arc::new(schedule_engine),
provider_registry,
cookie_key,
#[cfg(feature = "auth-oidc")]
oidc_service,
#[cfg(feature = "auth-jwt")]
jwt_validator,
config: Arc::new(config),
#[cfg(feature = "local-files")]
local_index: None,
#[cfg(feature = "local-files")]
transcode_manager: None,
#[cfg(feature = "local-files")]
sqlite_pool: None,
})
}
}
impl FromRef<AppState> for Arc<UserService> {
fn from_ref(input: &AppState) -> Self {
input.user_service.clone()
}
}
impl FromRef<AppState> for Arc<Config> {
fn from_ref(input: &AppState) -> Self {
input.config.clone()
}
}
impl FromRef<AppState> for Key {
fn from_ref(input: &AppState) -> Self {
input.cookie_key.clone()
}
}