feat: add find_last_slot_per_block method to schedule repositories and update related logic
This commit is contained in:
@@ -73,6 +73,10 @@ impl JellyfinMediaProvider {
|
||||
// requested — season first, then episode within the season.
|
||||
params.push(("SortBy", "ParentIndexNumber,IndexNumber".into()));
|
||||
params.push(("SortOrder", "Ascending".into()));
|
||||
// Prevent Jellyfin from returning Season/Series container items.
|
||||
if filter.content_type.is_none() {
|
||||
params.push(("IncludeItemTypes", "Episode".into()));
|
||||
}
|
||||
} else {
|
||||
// No series filter — scope to the collection (library) if one is set.
|
||||
if let Some(parent_id) = filter.collections.first() {
|
||||
|
||||
@@ -29,6 +29,12 @@ pub(super) struct SlotRow {
|
||||
pub source_block_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromRow)]
|
||||
pub(super) struct LastSlotRow {
|
||||
pub source_block_id: String,
|
||||
pub item: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromRow)]
|
||||
pub(super) struct PlaybackRecordRow {
|
||||
pub id: String,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use domain::{ChannelId, DomainError, DomainResult, GeneratedSchedule, PlaybackRecord, ScheduleRepository};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::mapping::{map_schedule, PlaybackRecordRow, ScheduleRow, SlotRow};
|
||||
use domain::{BlockId, ChannelId, DomainError, DomainResult, GeneratedSchedule, MediaItemId, PlaybackRecord, ScheduleRepository};
|
||||
|
||||
use super::mapping::{map_schedule, LastSlotRow, PlaybackRecordRow, ScheduleRow, SlotRow};
|
||||
|
||||
pub struct PostgresScheduleRepository {
|
||||
pool: sqlx::Pool<sqlx::Postgres>,
|
||||
@@ -143,6 +145,41 @@ impl ScheduleRepository for PostgresScheduleRepository {
|
||||
rows.into_iter().map(PlaybackRecord::try_from).collect()
|
||||
}
|
||||
|
||||
async fn find_last_slot_per_block(
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
) -> DomainResult<HashMap<BlockId, MediaItemId>> {
|
||||
let channel_id_str = channel_id.to_string();
|
||||
let rows: Vec<LastSlotRow> = sqlx::query_as(
|
||||
"SELECT ss.source_block_id, ss.item \
|
||||
FROM scheduled_slots ss \
|
||||
INNER JOIN generated_schedules gs ON gs.id = ss.schedule_id \
|
||||
WHERE gs.channel_id = $1 \
|
||||
AND ss.start_at = ( \
|
||||
SELECT MAX(ss2.start_at) \
|
||||
FROM scheduled_slots ss2 \
|
||||
INNER JOIN generated_schedules gs2 ON gs2.id = ss2.schedule_id \
|
||||
WHERE ss2.source_block_id = ss.source_block_id \
|
||||
AND gs2.channel_id = $2 \
|
||||
)",
|
||||
)
|
||||
.bind(&channel_id_str)
|
||||
.bind(&channel_id_str)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| DomainError::RepositoryError(e.to_string()))?;
|
||||
|
||||
let mut map = HashMap::new();
|
||||
for row in rows {
|
||||
let block_id = uuid::Uuid::parse_str(&row.source_block_id)
|
||||
.map_err(|e| DomainError::RepositoryError(format!("Invalid block UUID: {}", e)))?;
|
||||
let item: domain::MediaItem = serde_json::from_str(&row.item)
|
||||
.map_err(|e| DomainError::RepositoryError(format!("Invalid slot item JSON: {}", e)))?;
|
||||
map.insert(block_id, item.id);
|
||||
}
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
async fn save_playback_record(&self, record: &PlaybackRecord) -> DomainResult<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use domain::{ChannelId, DomainError, DomainResult, GeneratedSchedule, PlaybackRecord, ScheduleRepository};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::mapping::{map_schedule, PlaybackRecordRow, ScheduleRow, SlotRow};
|
||||
use domain::{BlockId, ChannelId, DomainError, DomainResult, GeneratedSchedule, MediaItemId, PlaybackRecord, ScheduleRepository};
|
||||
|
||||
use super::mapping::{map_schedule, LastSlotRow, PlaybackRecordRow, ScheduleRow, SlotRow};
|
||||
|
||||
pub struct SqliteScheduleRepository {
|
||||
pool: sqlx::SqlitePool,
|
||||
@@ -146,6 +148,41 @@ impl ScheduleRepository for SqliteScheduleRepository {
|
||||
rows.into_iter().map(PlaybackRecord::try_from).collect()
|
||||
}
|
||||
|
||||
async fn find_last_slot_per_block(
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
) -> DomainResult<HashMap<BlockId, MediaItemId>> {
|
||||
let channel_id_str = channel_id.to_string();
|
||||
let rows: Vec<LastSlotRow> = sqlx::query_as(
|
||||
"SELECT ss.source_block_id, ss.item \
|
||||
FROM scheduled_slots ss \
|
||||
INNER JOIN generated_schedules gs ON gs.id = ss.schedule_id \
|
||||
WHERE gs.channel_id = ? \
|
||||
AND ss.start_at = ( \
|
||||
SELECT MAX(ss2.start_at) \
|
||||
FROM scheduled_slots ss2 \
|
||||
INNER JOIN generated_schedules gs2 ON gs2.id = ss2.schedule_id \
|
||||
WHERE ss2.source_block_id = ss.source_block_id \
|
||||
AND gs2.channel_id = ? \
|
||||
)",
|
||||
)
|
||||
.bind(&channel_id_str)
|
||||
.bind(&channel_id_str)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| DomainError::RepositoryError(e.to_string()))?;
|
||||
|
||||
let mut map = HashMap::new();
|
||||
for row in rows {
|
||||
let block_id = uuid::Uuid::parse_str(&row.source_block_id)
|
||||
.map_err(|e| DomainError::RepositoryError(format!("Invalid block UUID: {}", e)))?;
|
||||
let item: domain::MediaItem = serde_json::from_str(&row.item)
|
||||
.map_err(|e| DomainError::RepositoryError(format!("Invalid slot item JSON: {}", e)))?;
|
||||
map.insert(block_id, item.id);
|
||||
}
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
async fn save_playback_record(&self, record: &PlaybackRecord) -> DomainResult<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
|
||||
Reference in New Issue
Block a user