181 lines
6.1 KiB
Rust
181 lines
6.1 KiB
Rust
//! Repository ports (traits)
|
|
//!
|
|
//! These traits define the interface for data persistence.
|
|
//! Implementations live in the infra layer.
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use async_trait::async_trait;
|
|
use chrono::DateTime;
|
|
use chrono::Utc;
|
|
use uuid::Uuid;
|
|
|
|
use crate::entities::{Channel, ChannelConfigSnapshot, GeneratedSchedule, PlaybackRecord, ScheduleConfig, User};
|
|
use crate::errors::DomainResult;
|
|
use crate::value_objects::{BlockId, ChannelId, MediaItemId, UserId};
|
|
|
|
/// An in-app activity event stored in the database for the admin log view.
|
|
#[derive(Debug, Clone)]
|
|
pub struct ActivityEvent {
|
|
pub id: Uuid,
|
|
pub timestamp: DateTime<Utc>,
|
|
pub event_type: String,
|
|
pub detail: String,
|
|
pub channel_id: Option<Uuid>,
|
|
}
|
|
|
|
/// Repository port for User persistence
|
|
#[async_trait]
|
|
pub trait UserRepository: Send + Sync {
|
|
/// Find a user by their internal ID
|
|
async fn find_by_id(&self, id: Uuid) -> DomainResult<Option<User>>;
|
|
|
|
/// Find a user by their OIDC subject (used for authentication)
|
|
async fn find_by_subject(&self, subject: &str) -> DomainResult<Option<User>>;
|
|
|
|
/// Find a user by their email
|
|
async fn find_by_email(&self, email: &str) -> DomainResult<Option<User>>;
|
|
|
|
/// Save a new user or update an existing one
|
|
async fn save(&self, user: &User) -> DomainResult<()>;
|
|
|
|
/// Delete a user by their ID
|
|
async fn delete(&self, id: Uuid) -> DomainResult<()>;
|
|
|
|
/// Count total number of users (used for first-user admin promotion)
|
|
async fn count_users(&self) -> DomainResult<u64>;
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct ProviderConfigRow {
|
|
pub provider_type: String,
|
|
pub config_json: String,
|
|
pub enabled: bool,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait ProviderConfigRepository: Send + Sync {
|
|
async fn get_all(&self) -> DomainResult<Vec<ProviderConfigRow>>;
|
|
async fn upsert(&self, row: &ProviderConfigRow) -> DomainResult<()>;
|
|
async fn delete(&self, provider_type: &str) -> DomainResult<()>;
|
|
}
|
|
|
|
/// Repository port for `Channel` persistence.
|
|
#[async_trait]
|
|
pub trait ChannelRepository: Send + Sync {
|
|
async fn find_by_id(&self, id: ChannelId) -> DomainResult<Option<Channel>>;
|
|
async fn find_by_owner(&self, owner_id: UserId) -> DomainResult<Vec<Channel>>;
|
|
async fn find_all(&self) -> DomainResult<Vec<Channel>>;
|
|
async fn find_auto_schedule_enabled(&self) -> DomainResult<Vec<Channel>>;
|
|
/// Insert or update a channel.
|
|
async fn save(&self, channel: &Channel) -> DomainResult<()>;
|
|
async fn delete(&self, id: ChannelId) -> DomainResult<()>;
|
|
|
|
/// Snapshot the current config before saving a new one.
|
|
/// version_num is computed by the infra layer as MAX(version_num)+1 inside a transaction.
|
|
async fn save_config_snapshot(
|
|
&self,
|
|
channel_id: ChannelId,
|
|
config: &ScheduleConfig,
|
|
label: Option<String>,
|
|
) -> DomainResult<ChannelConfigSnapshot>;
|
|
|
|
async fn list_config_snapshots(
|
|
&self,
|
|
channel_id: ChannelId,
|
|
) -> DomainResult<Vec<ChannelConfigSnapshot>>;
|
|
|
|
async fn get_config_snapshot(
|
|
&self,
|
|
channel_id: ChannelId,
|
|
snapshot_id: Uuid,
|
|
) -> DomainResult<Option<ChannelConfigSnapshot>>;
|
|
|
|
async fn patch_config_snapshot_label(
|
|
&self,
|
|
channel_id: ChannelId,
|
|
snapshot_id: Uuid,
|
|
label: Option<String>,
|
|
) -> DomainResult<Option<ChannelConfigSnapshot>>;
|
|
}
|
|
|
|
/// Repository port for `GeneratedSchedule` and `PlaybackRecord` persistence.
|
|
#[async_trait]
|
|
pub trait ScheduleRepository: Send + Sync {
|
|
/// Find the schedule whose `[valid_from, valid_until)` window contains `at`.
|
|
async fn find_active(
|
|
&self,
|
|
channel_id: ChannelId,
|
|
at: DateTime<Utc>,
|
|
) -> DomainResult<Option<GeneratedSchedule>>;
|
|
|
|
/// Find the most recently generated schedule for a channel.
|
|
/// Used to derive the next generation number.
|
|
async fn find_latest(
|
|
&self,
|
|
channel_id: ChannelId,
|
|
) -> DomainResult<Option<GeneratedSchedule>>;
|
|
|
|
/// Insert or replace a generated schedule.
|
|
async fn save(&self, schedule: &GeneratedSchedule) -> DomainResult<()>;
|
|
|
|
/// All playback records for a channel, used by the recycle policy engine.
|
|
async fn find_playback_history(
|
|
&self,
|
|
channel_id: ChannelId,
|
|
) -> DomainResult<Vec<PlaybackRecord>>;
|
|
|
|
async fn save_playback_record(&self, record: &PlaybackRecord) -> DomainResult<()>;
|
|
|
|
/// Return the most recent slot per block_id across ALL schedules for a channel.
|
|
/// Resilient to any single generation having empty slots for a block.
|
|
async fn find_last_slot_per_block(
|
|
&self,
|
|
channel_id: ChannelId,
|
|
) -> DomainResult<HashMap<BlockId, MediaItemId>>;
|
|
|
|
/// List all generated schedule headers for a channel, newest first.
|
|
async fn list_schedule_history(
|
|
&self,
|
|
channel_id: ChannelId,
|
|
) -> DomainResult<Vec<GeneratedSchedule>>;
|
|
|
|
/// Fetch a specific schedule with its slots, verifying channel ownership.
|
|
async fn get_schedule_by_id(
|
|
&self,
|
|
channel_id: ChannelId,
|
|
schedule_id: Uuid,
|
|
) -> DomainResult<Option<GeneratedSchedule>>;
|
|
|
|
/// Delete all schedules with generation > target_generation for this channel.
|
|
/// Also deletes matching playback_records (no DB cascade between those tables).
|
|
/// scheduled_slots cascade via FK from generated_schedules.
|
|
async fn delete_schedules_after(
|
|
&self,
|
|
channel_id: ChannelId,
|
|
target_generation: u32,
|
|
) -> DomainResult<()>;
|
|
}
|
|
|
|
/// Repository port for activity log persistence.
|
|
#[async_trait]
|
|
pub trait ActivityLogRepository: Send + Sync {
|
|
async fn log(
|
|
&self,
|
|
event_type: &str,
|
|
detail: &str,
|
|
channel_id: Option<Uuid>,
|
|
) -> DomainResult<()>;
|
|
async fn recent(&self, limit: u32) -> DomainResult<Vec<ActivityEvent>>;
|
|
}
|
|
|
|
/// Repository port for transcode settings persistence.
|
|
#[async_trait]
|
|
pub trait TranscodeSettingsRepository: Send + Sync {
|
|
/// Load the persisted cleanup TTL. Returns None if no row exists yet.
|
|
async fn load_cleanup_ttl(&self) -> DomainResult<Option<u32>>;
|
|
/// Persist the cleanup TTL (upsert — always row id=1).
|
|
async fn save_cleanup_ttl(&self, hours: u32) -> DomainResult<()>;
|
|
}
|