feat: add access control to channels with various modes

- Introduced AccessMode enum to define channel access levels: Public, PasswordProtected, AccountRequired, and OwnerOnly.
- Updated Channel and ProgrammingBlock entities to include access_mode and access_password_hash fields.
- Enhanced create and update channel functionality to handle access mode and password.
- Implemented access checks in channel routes based on the defined access modes.
- Modified frontend components to support channel creation and editing with access control options.
- Added ChannelPasswordModal for handling password input when accessing restricted channels.
- Updated API calls to include channel and block passwords as needed.
- Created database migrations to add access_mode and access_password_hash columns to channels table.
This commit is contained in:
2026-03-14 01:45:10 +01:00
parent 924e162563
commit 81df6eb8ff
25 changed files with 635 additions and 53 deletions

View File

@@ -58,6 +58,9 @@ pub struct CreateChannelRequest {
pub description: Option<String>,
/// IANA timezone, e.g. "UTC" or "America/New_York"
pub timezone: String,
pub access_mode: Option<domain::AccessMode>,
/// Plain-text password; hashed before storage.
pub access_password: Option<String>,
}
/// All fields are optional — only provided fields are updated.
@@ -70,6 +73,9 @@ pub struct UpdateChannelRequest {
pub schedule_config: Option<domain::ScheduleConfig>,
pub recycle_policy: Option<domain::RecyclePolicy>,
pub auto_schedule: Option<bool>,
pub access_mode: Option<domain::AccessMode>,
/// Empty string clears the password; non-empty re-hashes.
pub access_password: Option<String>,
}
#[derive(Debug, Serialize)]
@@ -82,6 +88,7 @@ pub struct ChannelResponse {
pub schedule_config: domain::ScheduleConfig,
pub recycle_policy: domain::RecyclePolicy,
pub auto_schedule: bool,
pub access_mode: domain::AccessMode,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
@@ -97,6 +104,7 @@ impl From<domain::Channel> for ChannelResponse {
schedule_config: c.schedule_config,
recycle_policy: c.recycle_policy,
auto_schedule: c.auto_schedule,
access_mode: c.access_mode,
created_at: c.created_at,
updated_at: c.updated_at,
}
@@ -147,6 +155,8 @@ pub struct ScheduledSlotResponse {
pub end_at: DateTime<Utc>,
pub item: MediaItemResponse,
pub source_block_id: Uuid,
#[serde(default)]
pub block_access_mode: domain::AccessMode,
}
impl From<domain::ScheduledSlot> for ScheduledSlotResponse {
@@ -157,6 +167,27 @@ impl From<domain::ScheduledSlot> for ScheduledSlotResponse {
end_at: s.end_at,
item: s.item.into(),
source_block_id: s.source_block_id,
block_access_mode: domain::AccessMode::default(),
}
}
}
impl ScheduledSlotResponse {
pub fn with_block_access(slot: domain::ScheduledSlot, channel: &domain::Channel) -> Self {
let block_access_mode = channel
.schedule_config
.blocks
.iter()
.find(|b| b.id == slot.source_block_id)
.map(|b| b.access_mode.clone())
.unwrap_or_default();
Self {
id: slot.id,
start_at: slot.start_at,
end_at: slot.end_at,
item: slot.item.into(),
source_block_id: slot.source_block_id,
block_access_mode,
}
}
}
@@ -169,6 +200,8 @@ pub struct CurrentBroadcastResponse {
/// Seconds elapsed since the start of the current item — use this as the
/// initial seek position for the player.
pub offset_secs: u32,
/// Access mode of the block currently playing. The stream is gated by this.
pub block_access_mode: domain::AccessMode,
}
#[derive(Debug, Serialize)]