use std::sync::Arc; use uuid::Uuid; use crate::entities::{Channel, ChannelConfigSnapshot, ScheduleConfig}; use crate::errors::{DomainError, DomainResult}; use crate::repositories::ChannelRepository; use crate::value_objects::{ChannelId, UserId}; /// Service for managing channels (CRUD + ownership enforcement). pub struct ChannelService { channel_repo: Arc, } impl ChannelService { pub fn new(channel_repo: Arc) -> Self { Self { channel_repo } } pub async fn create( &self, owner_id: UserId, name: &str, timezone: &str, ) -> DomainResult { let channel = Channel::new(owner_id, name, timezone); self.channel_repo.save(&channel).await?; Ok(channel) } pub async fn find_by_id(&self, id: ChannelId) -> DomainResult { self.channel_repo .find_by_id(id) .await? .ok_or(DomainError::ChannelNotFound(id)) } pub async fn find_all(&self) -> DomainResult> { self.channel_repo.find_all().await } pub async fn find_by_owner(&self, owner_id: UserId) -> DomainResult> { self.channel_repo.find_by_owner(owner_id).await } pub async fn update(&self, channel: Channel) -> DomainResult { // Auto-snapshot the existing config before overwriting if let Some(existing) = self.channel_repo.find_by_id(channel.id).await? { self.channel_repo .save_config_snapshot(channel.id, &existing.schedule_config, None) .await?; } self.channel_repo.save(&channel).await?; Ok(channel) } pub async fn list_config_snapshots( &self, channel_id: ChannelId, ) -> DomainResult> { self.channel_repo.list_config_snapshots(channel_id).await } pub async fn get_config_snapshot( &self, channel_id: ChannelId, snapshot_id: Uuid, ) -> DomainResult> { self.channel_repo.get_config_snapshot(channel_id, snapshot_id).await } pub async fn patch_config_snapshot_label( &self, channel_id: ChannelId, snapshot_id: Uuid, label: Option, ) -> DomainResult> { self.channel_repo.patch_config_snapshot_label(channel_id, snapshot_id, label).await } /// Restore a snapshot: auto-snapshot current config, then apply the snapshot's config. pub async fn restore_config_snapshot( &self, channel_id: ChannelId, snapshot_id: Uuid, ) -> DomainResult { let snapshot = self .channel_repo .get_config_snapshot(channel_id, snapshot_id) .await? .ok_or(DomainError::ChannelNotFound(channel_id))?; let mut channel = self .channel_repo .find_by_id(channel_id) .await? .ok_or(DomainError::ChannelNotFound(channel_id))?; // Snapshot current config before overwriting self.channel_repo .save_config_snapshot(channel_id, &channel.schedule_config, None) .await?; channel.schedule_config = snapshot.config; channel.updated_at = chrono::Utc::now(); self.channel_repo.save(&channel).await?; Ok(channel) } pub async fn save_config_snapshot( &self, channel_id: ChannelId, config: &ScheduleConfig, label: Option, ) -> DomainResult { self.channel_repo.save_config_snapshot(channel_id, config, label).await } /// Delete a channel, enforcing that `requester_id` is the owner. pub async fn delete(&self, id: ChannelId, requester_id: UserId) -> DomainResult<()> { let channel = self.find_by_id(id).await?; if channel.owner_id != requester_id { return Err(DomainError::forbidden("You don't own this channel")); } self.channel_repo.delete(id).await } }