Repository: IgnisDa/ryot (language: rust)

Issue:
invalid Audiobook plays
I somehow have over 700 plays for one audiobook in ryot that i defnitely only have finished once. 
![image](https://github.com/user-attachments/assets/146776ae-db90-40ee-8031-86a7a61b77ac)
all of the invalid plays don't have the started date set
![image](https://github.com/user-attachments/assets/264b18a0-813f-43ae-a78b-19cbe8604ffa)
![image](https://github.com/user-attachments/assets/be75b6a5-bdec-48dc-bec4-13e90d53ad17)

All those plays were added through the audiobookshelf integration.

i currently have a stuck import job for audiobookshelf running, but that issue has existed before that as well


Discussion / hints:
Do you have ABS integration setup? Or were these imported through the importer?
BTW, you have Rick Riordan in your library so I already like you :)
> Do you have ABS integration setup? Or were these imported through the importer?

i am not sure through which it came. i have used both

Edit: i think' it's more likely to come from the integration, since the import dates are mostly quite spread out
According to the discussion you opened, it might be possible that you ran the import multiple times. 

Would it be possible for you to start a fresh Ryot instance and only run the importer once and then setup the integration? If the issue still persists, then I would consider it a bug.
I will try to reproduce it from a clean install. copying the docker compose file to a new folder shouldn't be much of an issue :D 
i set up a new instance and imported audiobookshelf data and added the integration. so far no anomalies.

after that i have started a trakt import just because why not. it is still going but has already imported 207 plays of a movie i have never watched before :D
Thats weird. What is your trakt username? 

If possible, please join the Discord (link in readme). It would be easier to debug there.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 9a2051db4a..6f6db7d11d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -851,6 +851,7 @@ dependencies = [
  "aws-sdk-s3",
  "axum",
  "background",
+ "cache-service",
  "chrono",
  "chrono-tz",
  "collection-resolver",
@@ -1124,6 +1125,19 @@ dependencies = [
  "either",
 ]
 
+[[package]]
+name = "cache-service"
+version = "0.1.0"
+dependencies = [
+ "async-graphql",
+ "chrono",
+ "common-models",
+ "database-models",
+ "sea-orm",
+ "sea-query",
+ "uuid",
+]
+
 [[package]]
 name = "castaway"
 version = "0.2.3"
@@ -1309,10 +1323,12 @@ dependencies = [
  "async-graphql",
  "enum_meta",
  "enums",
+ "rust_decimal",
  "schematic",
  "sea-orm",
  "serde",
  "serde_json",
+ "serde_with 3.9.0",
  "strum",
 ]
 
@@ -3784,6 +3800,7 @@ dependencies = [
  "application-utils",
  "async-graphql",
  "background",
+ "cache-service",
  "chrono",
  "chrono-tz",
  "common-models",
@@ -3792,7 +3809,6 @@ dependencies = [
  "database-models",
  "database-utils",
  "dependent-models",
- "derive_more 1.0.0",
  "enums",
  "env-utils",
  "file-storage-service",
diff --git a/Cargo.toml b/Cargo.toml
index 1acd1463a9..40d8c66bf3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,6 +23,7 @@ members = [
     "crates/resolvers/statistics",
     "crates/resolvers/router",
     "crates/resolvers/user",
+    "crates/services/cache",
     "crates/services/collection",
     "crates/services/exporter",
     "crates/services/file-storage",
diff --git a/apps/backend/Cargo.toml b/apps/backend/Cargo.toml
index b786679b1b..fb0ce386d0 100644
--- a/apps/backend/Cargo.toml
+++ b/apps/backend/Cargo.toml
@@ -14,6 +14,7 @@ async-graphql-axum = "=7.0.9"
 axum = { workspace = true }
 aws-sdk-s3 = { workspace = true }
 background = { path = "../../crates/background" }
+cache-service = { path = "../../crates/services/cache" }
 chrono = { workspace = true }
 chrono-tz = { workspace = true }
 collection-resolver = { path = "../../crates/resolvers/collection" }
diff --git a/apps/backend/src/common.rs b/apps/backend/src/common.rs
index a194ef9aec..990435c413 100644
--- a/apps/backend/src/common.rs
+++ b/apps/backend/src/common.rs
@@ -11,6 +11,7 @@ use axum::{
     Extension,
 };
 use background::{ApplicationJob, CoreApplicationJob};
+use cache_service::CacheService;
 use collection_resolver::{CollectionMutation, CollectionQuery};
 use collection_service::CollectionService;
 use common_utils::{ryot_log, FRONTEND_OAUTH_ENDPOINT};
@@ -76,6 +77,7 @@ pub async fn create_app_services(
     ));
     let oidc_client = Arc::new(create_oidc_client(&config).await);
 
+    let cache_service = Arc::new(CacheService::new(&db));
     let collection_service = Arc::new(CollectionService::new(
         &db,
         config.clone(),
@@ -88,6 +90,7 @@ pub async fn create_app_services(
             &db,
             timezone.clone(),
             config.clone(),
+            cache_service.clone(),
             file_storage_service.clone(),
             perform_application_job,
             perform_core_application_job,
diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs
index 7aaf810d7c..0bd4ae85f6 100644
--- a/crates/migrations/src/lib.rs
+++ b/crates/migrations/src/lib.rs
@@ -40,6 +40,7 @@ mod m20240923_remove_extra_columns_for_daily_user_activities;
 mod m20240926_add_columns_for_open_sourcing_pro_version;
 mod m20240928_add_grid_packing_to_general_preferences;
 mod m20241002_add_columns_for_associating_seen_with_reviews;
+mod m20241004_create_application_cache;
 
 pub use m20230410_create_metadata::Metadata as AliasedMetadata;
 pub use m20230413_create_person::Person as AliasedPerson;
@@ -100,6 +101,7 @@ impl MigratorTrait for Migrator {
             Box::new(m20240926_add_columns_for_open_sourcing_pro_version::Migration),
             Box::new(m20240928_add_grid_packing_to_general_preferences::Migration),
             Box::new(m20241002_add_columns_for_associating_seen_with_reviews::Migration),
+            Box::new(m20241004_create_application_cache::Migration),
         ]
     }
 }
diff --git a/crates/migrations/src/m20241004_create_application_cache.rs b/crates/migrations/src/m20241004_create_application_cache.rs
new file mode 100644
index 0000000000..1fb661360e
--- /dev/null
+++ b/crates/migrations/src/m20241004_create_application_cache.rs
@@ -0,0 +1,51 @@
+use sea_orm_migration::prelude::*;
+
+#[derive(DeriveMigrationName)]
+pub struct Migration;
+
+#[derive(Iden)]
+pub enum ApplicationCache {
+    Table,
+    Id,
+    Key,
+    ExpiresAt,
+    CreatedAt,
+}
+
+#[async_trait::async_trait]
+impl MigrationTrait for Migration {
+    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
+        manager
+            .create_table(
+                Table::create()
+                    .table(ApplicationCache::Table)
+                    .col(
+                        ColumnDef::new(ApplicationCache::Id)
+                            .uuid()
+                            .not_null()
+                            .default(PgFunc::gen_random_uuid())
+                            .primary_key(),
+                    )
+                    .col(
+                        ColumnDef::new(ApplicationCache::CreatedAt)
+                            .timestamp_with_time_zone()
+                            .not_null()
+                            .default(Expr::current_timestamp()),
+                    )
+                    .col(
+                        ColumnDef::new(ApplicationCache::Key)
+                            .json_binary()
+                            .not_null()
+                            .unique_key(),
+                    )
+                    .col(ColumnDef::new(ApplicationCache::ExpiresAt).timestamp_with_time_zone())
+                    .to_owned(),
+            )
+            .await?;
+        Ok(())
+    }
+
+    async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
+        Ok(())
+    }
+}
diff --git a/crates/models/common/Cargo.toml b/crates/models/common/Cargo.toml
index 3763b67e43..9afa46bcbf 100644
--- a/crates/models/common/Cargo.toml
+++ b/crates/models/common/Cargo.toml
@@ -7,8 +7,10 @@ edition = "2021"
 async-graphql = { workspace = true }
 enums = { path = "../../enums" }
 enum_meta = { workspace = true }
+rust_decimal = { workspace = true }
 schematic = { workspace = true }
 sea-orm = { workspace = true }
 serde = { workspace = true }
 serde_json = { workspace = true }
+serde_with = { workspace = true }
 strum = { workspace = true }
diff --git a/crates/models/common/src/lib.rs b/crates/models/common/src/lib.rs
index 3fcfe26829..0ba2f58ac8 100644
--- a/crates/models/common/src/lib.rs
+++ b/crates/models/common/src/lib.rs
@@ -1,9 +1,11 @@
 use async_graphql::{Enum, InputObject, SimpleObject};
 use enum_meta::{meta, Meta};
 use enums::EntityLot;
+use rust_decimal::Decimal;
 use schematic::{ConfigEnum, Schematic};
 use sea_orm::{prelude::DateTimeUtc, FromJsonQueryResult};
 use serde::{Deserialize, Serialize};
+use serde_with::skip_serializing_none;
 use strum::{Display, EnumIter};
 
 #[derive(Debug, SimpleObject, Serialize, Deserialize, Clone)]
@@ -195,3 +197,18 @@ pub struct ExportJob {
     pub ended_at: DateTimeUtc,
     pub started_at: DateTimeUtc,
 }
+
+#[skip_serializing_none]
+#[derive(Clone, Debug, PartialEq, FromJsonQueryResult, Eq, Serialize, Deserialize)]
+pub enum ApplicationCacheKey {
+    ProgressUpdateCache {
+        user_id: String,
+        metadata_id: String,
+        show_season_number: Option<i32>,
+        show_episode_number: Option<i32>,
+        podcast_episode_number: Option<i32>,
+        anime_episode_number: Option<i32>,
+        manga_chapter_number: Option<Decimal>,
+        manga_volume_number: Option<i32>,
+    },
+}
diff --git a/crates/models/database/src/application_cache.rs b/crates/models/database/src/application_cache.rs
new file mode 100644
index 0000000000..a3d1249a59
--- /dev/null
+++ b/crates/models/database/src/application_cache.rs
@@ -0,0 +1,20 @@
+//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
+
+use common_models::ApplicationCacheKey;
+use sea_orm::entity::prelude::*;
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[sea_orm(table_name = "application_cache")]
+pub struct Model {
+    #[sea_orm(primary_key)]
+    pub id: Uuid,
+    pub created_at: DateTimeUtc,
+    #[sea_orm(column_type = "Json")]
+    pub key: ApplicationCacheKey,
+    pub expires_at: Option<DateTimeUtc>,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/crates/models/database/src/lib.rs b/crates/models/database/src/lib.rs
index 729f63d3d2..127aefdca2 100644
--- a/crates/models/database/src/lib.rs
+++ b/crates/models/database/src/lib.rs
@@ -1,8 +1,9 @@
-//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15
+//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
 
 pub mod prelude;
 
 pub mod access_link;
+pub mod application_cache;
 pub mod calendar_event;
 pub mod collection;
 pub mod collection_to_entity;
diff --git a/crates/models/database/src/prelude.rs b/crates/models/database/src/prelude.rs
index 4af84d5546..6a216d9f56 100644
--- a/crates/models/database/src/prelude.rs
+++ b/crates/models/database/src/prelude.rs
@@ -1,6 +1,7 @@
-//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15
+//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
 
 pub use super::access_link::Entity as AccessLink;
+pub use super::application_cache::Entity as ApplicationCache;
 pub use super::calendar_event::Entity as CalendarEvent;
 pub use super::collection::Entity as Collection;
 pub use super::collection_to_entity::Entity as CollectionToEntity;
diff --git a/crates/services/cache/Cargo.toml b/crates/services/cache/Cargo.toml
new file mode 100644
index 0000000000..129a158a2b
--- /dev/null
+++ b/crates/services/cache/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "cache-service"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+async-graphql = { workspace = true }
+chrono = { workspace = true }
+common-models = { path = "../../models/common" }
+database-models = { path = "../../models/database" }
+sea-orm = { workspace = true }
+sea-query = { workspace = true }
+uuid = { workspace = true }
diff --git a/crates/services/cache/src/lib.rs b/crates/services/cache/src/lib.rs
new file mode 100644
index 0000000000..ac794229fe
--- /dev/null
+++ b/crates/services/cache/src/lib.rs
@@ -0,0 +1,67 @@
+use async_graphql::Result;
+use chrono::{Duration, Utc};
+use common_models::ApplicationCacheKey;
+use database_models::{application_cache, prelude::ApplicationCache};
+use sea_orm::{ActiveValue, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter};
+use sea_query::OnConflict;
+use uuid::Uuid;
+
+pub struct CacheService {
+    db: DatabaseConnection,
+}
+
+impl CacheService {
+    pub fn new(db: &DatabaseConnection) -> Self {
+        Self { db: db.clone() }
+    }
+}
+
+impl CacheService {
+    pub async fn set_with_expiry(
+        &self,
+        key: ApplicationCacheKey,
+        expiry_hours: i64,
+    ) -> Result<Uuid> {
+        let now = Utc::now();
+        let to_insert = application_cache::ActiveModel {
+            key: ActiveValue::Set(key),
+            expires_at: ActiveValue::Set(Some(now + Duration::hours(expiry_hours))),
+            created_at: ActiveValue::Set(now),
+            ..Default::default()
+        };
+        let inserted = ApplicationCache::insert(to_insert)
+            .on_conflict(
+                OnConflict::column(application_cache::Column::Key)
+                    .update_columns([
+                        application_cache::Column::ExpiresAt,
+                        application_cache::Column::CreatedAt,
+                    ])
+                    .to_owned(),
+            )
+            .exec(&self.db)
+            .await?;
+        Ok(inserted.last_insert_id)
+    }
+
+    pub async fn get(&self, key: ApplicationCacheKey) -> Result<Option<()>> {
+        let cache = ApplicationCache::find()
+            .filter(application_cache::Column::Key.eq(key))
+            .one(&self.db)
+            .await?;
+        Ok(cache
+            .filter(|cache| {
+                cache
+                    .expires_at
+                    .map_or(false, |expires_at| expires_at > Utc::now())
+            })
+            .map(|_| ()))
+    }
+
+    pub async fn delete(&self, key: ApplicationCacheKey) -> Result<bool> {
+        let deleted = ApplicationCache::delete_many()
+            .filter(application_cache::Column::Key.eq(key))
+            .exec(&self.db)
+            .await?;
+        Ok(deleted.rows_affected > 0)
+    }
+}
diff --git a/crates/services/importer/src/audiobookshelf.rs b/crates/services/importer/src/audiobookshelf.rs
index 476231f991..312b1221bb 100644
--- a/crates/services/importer/src/audiobookshelf.rs
+++ b/crates/services/importer/src/audiobookshelf.rs
@@ -141,7 +141,7 @@ where
                 } else {
                     ryot_log!(
                         debug,
-                        "No ASIN, ISBN or iTunes ID found for item {:#?}",
+                        "No ASIN, ISBN or iTunes ID found for item {:?}",
                         item
                     );
                     continue;
diff --git a/crates/services/integration/src/audiobookshelf.rs b/crates/services/integration/src/audiobookshelf.rs
index ef13f4f6a6..fae2fdd1b2 100644
--- a/crates/services/integration/src/audiobookshelf.rs
+++ b/crates/services/integration/src/audiobookshelf.rs
@@ -131,14 +131,14 @@ impl YankIntegrationWithCommit for AudiobookshelfIntegration {
                             }
                         }
                         _ => {
-                            ryot_log!(debug, "No recent episode found for item {:#?}", item);
+                            ryot_log!(debug, "No recent episode found for item {:?}", item);
                             continue;
                         }
                     }
                 } else {
                     ryot_log!(
                         debug,
-                        "No ASIN, ISBN or iTunes ID found for item {:#?}",
+                        "No ASIN, ISBN or iTunes ID found for item {:?}",
                         item
                     );
                     continue;
diff --git a/crates/services/miscellaneous/Cargo.toml b/crates/services/miscellaneous/Cargo.toml
index 030c436639..ac86404eb1 100644
--- a/crates/services/miscellaneous/Cargo.toml
+++ b/crates/services/miscellaneous/Cargo.toml
@@ -8,6 +8,7 @@ apalis = { workspace = true }
 application-utils = { path = "../../utils/application" }
 async-graphql = { workspace = true }
 background = { path = "../../background" }
+cache-service = { path = "../cache" }
 chrono = { workspace = true }
 chrono-tz = { workspace = true }
 common-models = { path = "../../models/common" }
@@ -16,7 +17,6 @@ config = { path = "../../config" }
 database-models = { path = "../../models/database" }
 database-utils = { path = "../../utils/database" }
 dependent-models = { path = "../../models/dependent" }
-derive_more = { workspace = true }
 enums = { path = "../../enums" }
 env-utils = { path = "../../utils/env" }
 file-storage-service = { path = "../../services/file-storage" }
diff --git a/crates/services/miscellaneous/src/lib.rs b/crates/services/miscellaneous/src/lib.rs
index 74201be12a..e38b976eac 100644
--- a/crates/services/miscellaneous/src/lib.rs
+++ b/crates/services/miscellaneous/src/lib.rs
@@ -9,23 +9,25 @@ use apalis::prelude::{MemoryStorage, MessageQueue};
 use application_utils::get_current_date;
 use async_graphql::{Enum, Error, Result};
 use background::{ApplicationJob, CoreApplicationJob};
+use cache_service::CacheService;
 use chrono::{Days, Duration as ChronoDuration, NaiveDate, Utc};
 use common_models::{
-    BackendError, BackgroundJob, ChangeCollectionToEntityInput, DefaultCollection,
-    IdAndNamedObject, MediaStateChanged, SearchDetails, SearchInput, StoredUrl, StringIdObject,
+    ApplicationCacheKey, BackendError, BackgroundJob, ChangeCollectionToEntityInput,
+    DefaultCollection, IdAndNamedObject, MediaStateChanged, SearchDetails, SearchInput, StoredUrl,
+    StringIdObject,
 };
 use common_utils::{
     get_first_and_last_day_of_month, ryot_log, IsFeatureEnabled, SHOW_SPECIAL_SEASON_NAMES,
 };
 use database_models::{
-    access_link, calendar_event, collection, collection_to_entity,
+    access_link, application_cache, calendar_event, collection, collection_to_entity,
     functions::{associate_user_with_entity, get_user_to_entity_association},
     genre, import_report, integration, metadata, metadata_group, metadata_to_genre,
     metadata_to_metadata, metadata_to_metadata_group, metadata_to_person, monitored_entity,
     notification_platform, person,
     prelude::{
-        AccessLink, CalendarEvent, Collection, CollectionToEntity, Genre, ImportReport,
-        Integration, Metadata, MetadataGroup, MetadataToGenre, MetadataToMetadata,
+        AccessLink, ApplicationCache, CalendarEvent, Collection, CollectionToEntity, Genre,
+        ImportReport, Integration, Metadata, MetadataGroup, MetadataToGenre, MetadataToMetadata,
         MetadataToMetadataGroup, MetadataToPerson, MonitoredEntity, NotificationPlatform, Person,
         QueuedNotification, Review, Seen, User, UserToEntity,
     },
@@ -124,18 +126,6 @@ impl MediaProviderLanguages for CustomService {
     }
 }
 
-#[derive(Debug, Ord, PartialEq, Eq, PartialOrd, Clone, Hash)]
-struct ProgressUpdateCache {
-    user_id: String,
-    metadata_id: String,
-    show_season_number: Option<i32>,
-    show_episode_number: Option<i32>,
-    podcast_episode_number: Option<i32>,
-    anime_episode_number: Option<i32>,
-    manga_chapter_number: Option<Decimal>,
-    manga_volume_number: Option<i32>,
-}
-
 #[derive(Debug, Ord, PartialEq, Eq, PartialOrd, Clone, Hash)]
 struct CommitCache {
     id: String,
@@ -148,9 +138,9 @@ pub struct MiscellaneousService {
     db: DatabaseConnection,
     timezone: Arc<chrono_tz::Tz>,
     config: Arc<config::AppConfig>,
+    cache_service: Arc<CacheService>,
     commit_cache: Cache<CommitCache, ()>,
     file_storage_service: Arc<FileStorageService>,
-    seen_progress_cache: Cache<ProgressUpdateCache, ()>,
     perform_application_job: MemoryStorage<ApplicationJob>,
     perform_core_application_job: MemoryStorage<CoreApplicationJob>,
 }
@@ -169,11 +159,11 @@ impl MiscellaneousService {
         db: &DatabaseConnection,
         timezone: Arc<chrono_tz::Tz>,
         config: Arc<config::AppConfig>,
+        cache_service: Arc<CacheService>,
         file_storage_service: Arc<FileStorageService>,
         perform_application_job: &MemoryStorage<ApplicationJob>,
         perform_core_application_job: &MemoryStorage<CoreApplicationJob>,
     ) -> Self {
-        let seen_progress_cache = create_disk_cache(config.server.progress_update_threshold);
         let commit_cache = create_disk_cache(1);
         Self {
             config,
@@ -181,8 +171,8 @@ impl MiscellaneousService {
             timezone,
             oidc_enabled,
             commit_cache,
+            cache_service,
             db: db.clone(),
-            seen_progress_cache,
             file_storage_service,
             perform_application_job: perform_application_job.clone(),
             perform_core_application_job: perform_core_application_job.clone(),
@@ -1133,17 +1123,17 @@ ORDER BY RANDOM() LIMIT 10;
         // DEV: update only if media has not been consumed for this user in the last `n` duration
         respect_cache: bool,
     ) -> Result<ProgressUpdateResultUnion> {
-        let cache = ProgressUpdateCache {
+        let cache = ApplicationCacheKey::ProgressUpdateCache {
             user_id: user_id.to_owned(),
             metadata_id: input.metadata_id.clone(),
             show_season_number: input.show_season_number,
             show_episode_number: input.show_episode_number,
-            podcast_episode_number: input.podcast_episode_number,
-            anime_episode_number: input.anime_episode_number,
-            manga_chapter_number: input.manga_chapter_number,
             manga_volume_number: input.manga_volume_number,
+            manga_chapter_number: input.manga_chapter_number,
+            anime_episode_number: input.anime_episode_number,
+            podcast_episode_number: input.podcast_episode_number,
         };
-        let in_cache = self.seen_progress_cache.get(&cache).await;
+        let in_cache = self.cache_service.get(cache.clone()).await?;
         if respect_cache && in_cache.is_some() {
             ryot_log!(debug, "Seen is already in cache");
             return Ok(ProgressUpdateResultUnion::Error(ProgressUpdateError {
@@ -1353,7 +1343,9 @@ ORDER BY RANDOM() LIMIT 10;
         ryot_log!(debug, "Progress update = {:?}", seen);
         let id = seen.id.clone();
         if seen.state == SeenState::Completed && respect_cache {
-            self.seen_progress_cache.insert(cache, ()).await;
+            self.cache_service
+                .set_with_expiry(cache, self.config.server.progress_update_threshold)
+                .await?;
         }
         self.after_media_seen_tasks(seen).await?;
         Ok(ProgressUpdateResultUnion::Ok(StringIdObject { id }))
@@ -2858,17 +2850,17 @@ ORDER BY RANDOM() LIMIT 10;
             let aen = si.anime_extra_information.as_ref().and_then(|d| d.episode);
             let mcn = si.manga_extra_information.as_ref().and_then(|d| d.chapter);
             let mvn = si.manga_extra_information.as_ref().and_then(|d| d.volume);
-            let cache = ProgressUpdateCache {
-                user_id: user_id.to_owned(),
-                metadata_id: si.metadata_id.clone(),
+            let cache = ApplicationCacheKey::ProgressUpdateCache {
                 show_season_number: ssn,
+                manga_volume_number: mvn,
                 show_episode_number: sen,
-                podcast_episode_number: pen,
                 anime_episode_number: aen,
                 manga_chapter_number: mcn,
-                manga_volume_number: mvn,
+                podcast_episode_number: pen,
+                user_id: user_id.to_owned(),
+                metadata_id: si.metadata_id.clone(),
             };
-            self.seen_progress_cache.remove(&cache).await;
+            self.cache_service.delete(cache).await?;
             let seen_id = si.id.clone();
             let metadata_id = si.metadata_id.clone();
             if &si.user_id != user_id {
@@ -4584,6 +4576,11 @@ ORDER BY RANDOM() LIMIT 10;
             .filter(access_link::Column::IsRevoked.eq(true))
             .exec(&self.db)
             .await?;
+        ryot_log!(debug, "Deleting expired application caches");
+        ApplicationCache::delete_many()
+            .filter(application_cache::Column::ExpiresAt.lt(Utc::now()))
+            .exec(&self.db)
+            .await?;
         Ok(())
     }
 


Test patch: