Repository: IgnisDa/ryot (language: rust)

Issue:
Feature: rearrange the dashboard a bit
i would like to throw an idea about the way dashboard looks. so you have "upcoming" first and then "in progress" but in reality if people is like me and they like to mark the episodes as watched, having "in progress" above "upcoming" sound better. 

also i am curious to know what are the criteria for upcoming? on my dashboard, i have s2 e5 of HoD then S4E8 of the boys then 3 more episodes of HoD and the days they are releasing. on the calendar, there is other shows that will be release in like 5 days that will not show on that list. also no movies will show on that list.



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8f3a138670..c21d64d763 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -130,6 +130,8 @@ jobs:
           tags: ${{ steps.required_args.outputs.image_names }}
           build-args: |
             APP_VERSION=${{ steps.required_args.outputs.APP_VERSION }}
+            DEFAULT_TMDB_ACCESS_TOKEN=${{ secrets.DEFAULT_TMDB_ACCESS_TOKEN }}
+            DEFAULT_MAL_CLIENT_ID=${{ secrets.DEFAULT_MAL_CLIENT_ID }}
 
   upload-kodi-plugin:
     runs-on: ubuntu-20.04
diff --git a/Cargo.lock b/Cargo.lock
index 28a784d6ce..facbc0c16d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1174,6 +1174,7 @@ name = "config"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "dotenvy_macro",
  "rs-utils",
  "schematic",
  "serde",
diff --git a/Cargo.toml b/Cargo.toml
index f9fe4448a8..f23d0dd5e9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,6 +11,7 @@ async-graphql = { version = "=7.0.6", features = [
     "tracing",
 ] }
 chrono = "=0.4.38"
+dotenvy_macro = "=0.15.7"
 nanoid = "=0.4.0"
 schematic = { version = "=0.16.4", features = [
     "config",
diff --git a/Dockerfile b/Dockerfile
index cacc4f3a15..bc17e1f982 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -31,22 +31,30 @@ COPY . .
 RUN cargo chef prepare --recipe-path recipe.json
 
 FROM backend-chef AS backend-builder
+# build specific
 ARG TARGETARCH
-ARG APP_VERSION
 ARG BUILD_PROFILE=release
+# application specific
+ARG APP_VERSION
+ARG DEFAULT_TMDB_ACCESS_TOKEN
+ARG DEFAULT_MAL_CLIENT_ID
+RUN test -n "$APP_VERSION" && \
+    test -n "$DEFAULT_TMDB_ACCESS_TOKEN" && \
+    test -n "$DEFAULT_MAL_CLIENT_ID"
 ENV RUST_TARGET_TRIPLE_arm64="aarch64-unknown-linux-gnu"
 ENV RUST_TARGET_TRIPLE_amd64="x86_64-unknown-linux-gnu"
 ENV TARGET_CC="clang"
 ENV TARGET_AR="llvm-ar"
 ENV CFLAGS_aarch64_unknown_linux_gnu="--sysroot=/usr/aarch64-linux-gnu"
 ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
-ENV APP_VERSION=$APP_VERSION
-RUN test -n "$APP_VERSION"
 COPY --from=backend-planner /app/recipe.json recipe.json
 RUN rustup target add $(eval "echo \$RUST_TARGET_TRIPLE_$TARGETARCH")
 RUN cargo chef cook --profile $BUILD_PROFILE --target $(eval "echo \$RUST_TARGET_TRIPLE_$TARGETARCH") --recipe-path recipe.json
 COPY . .
-RUN ./apps/backend/ci/build-app.sh
+RUN APP_VERSION=$APP_VERSION \
+    DEFAULT_TMDB_ACCESS_TOKEN=$DEFAULT_TMDB_ACCESS_TOKEN \
+    DEFAULT_MAL_CLIENT_ID=$DEFAULT_MAL_CLIENT_ID \
+    ./apps/backend/ci/build-app.sh
 
 FROM $NODE_BASE_IMAGE
 LABEL org.opencontainers.image.source="https://github.com/IgnisDa/ryot"
diff --git a/apps/backend/Cargo.toml b/apps/backend/Cargo.toml
index 40baf5cfa5..b526ee881b 100644
--- a/apps/backend/Cargo.toml
+++ b/apps/backend/Cargo.toml
@@ -31,7 +31,7 @@ derive_more = { version = "=1.0.0-beta.6", features = [
     "add_assign",
 ], default-features = false }
 dotenvy = "=0.15.7"
-dotenvy_macro = "=0.15.7"
+dotenvy_macro = { workspace = true }
 educe = { version = "=0.6.0", features = ["Debug"], default-features = false }
 enum_meta = "=0.7.0"
 flate2 = "=1.0.30"
diff --git a/apps/backend/src/exporter.rs b/apps/backend/src/exporter.rs
index a4d9b5c3ed..73cc058a02 100644
--- a/apps/backend/src/exporter.rs
+++ b/apps/backend/src/exporter.rs
@@ -29,12 +29,14 @@ struct ExportJob {
 #[derive(Default)]
 pub struct ExporterQuery;
 
+impl AuthProvider for ExporterQuery {}
+
 #[Object]
 impl ExporterQuery {
     /// Get all the export jobs for the current user.
     async fn user_exports(&self, gql_ctx: &Context<'_>) -> Result<Vec<ExportJob>> {
         let service = gql_ctx.data_unchecked::<Arc<ExporterService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.user_exports(user_id).await
     }
 }
@@ -42,6 +44,12 @@ impl ExporterQuery {
 #[derive(Default)]
 pub struct ExporterMutation;
 
+impl AuthProvider for ExporterMutation {
+    fn is_mutation(&self) -> bool {
+        true
+    }
+}
+
 #[Object]
 impl ExporterMutation {
     /// Deploy a job to export data for a user.
@@ -51,7 +59,7 @@ impl ExporterMutation {
         to_export: Vec<ExportItem>,
     ) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<ExporterService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.deploy_export_job(user_id, to_export).await
     }
 }
@@ -63,8 +71,6 @@ pub struct ExporterService {
     exercise_service: Arc<ExerciseService>,
 }
 
-impl AuthProvider for ExporterService {}
-
 impl ExporterService {
     pub fn new(
         config: Arc<config::AppConfig>,
diff --git a/apps/backend/src/fitness/resolver.rs b/apps/backend/src/fitness/resolver.rs
index 4603390ed5..095f44f382 100644
--- a/apps/backend/src/fitness/resolver.rs
+++ b/apps/backend/src/fitness/resolver.rs
@@ -128,6 +128,8 @@ struct EditCustomExerciseInput {
 #[derive(Default)]
 pub struct ExerciseQuery;
 
+impl AuthProvider for ExerciseQuery {}
+
 #[Object]
 impl ExerciseQuery {
     /// Get all the parameters related to exercises.
@@ -143,7 +145,7 @@ impl ExerciseQuery {
         input: ExercisesListInput,
     ) -> Result<SearchResults<ExerciseListItem>> {
         let service = gql_ctx.data_unchecked::<Arc<ExerciseService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.exercises_list(user_id, input).await
     }
 
@@ -154,7 +156,7 @@ impl ExerciseQuery {
         input: SearchInput,
     ) -> Result<SearchResults<WorkoutListItem>> {
         let service = gql_ctx.data_unchecked::<Arc<ExerciseService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.user_workout_list(user_id, input).await
     }
 
@@ -175,7 +177,7 @@ impl ExerciseQuery {
         workout_id: String,
     ) -> Result<workout::Model> {
         let service = gql_ctx.data_unchecked::<Arc<ExerciseService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.workout_details(&user_id, workout_id).await
     }
 
@@ -186,7 +188,7 @@ impl ExerciseQuery {
         exercise_id: String,
     ) -> Result<UserExerciseDetails> {
         let service = gql_ctx.data_unchecked::<Arc<ExerciseService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.user_exercise_details(user_id, exercise_id).await
     }
 
@@ -197,7 +199,7 @@ impl ExerciseQuery {
         input: UserMeasurementsListInput,
     ) -> Result<Vec<user_measurement::Model>> {
         let service = gql_ctx.data_unchecked::<Arc<ExerciseService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.user_measurements_list(&user_id, input).await
     }
 }
@@ -205,6 +207,12 @@ impl ExerciseQuery {
 #[derive(Default)]
 pub struct ExerciseMutation;
 
+impl AuthProvider for ExerciseMutation {
+    fn is_mutation(&self) -> bool {
+        true
+    }
+}
+
 #[Object]
 impl ExerciseMutation {
     /// Create a user measurement.
@@ -214,7 +222,7 @@ impl ExerciseMutation {
         input: user_measurement::Model,
     ) -> Result<DateTimeUtc> {
         let service = gql_ctx.data_unchecked::<Arc<ExerciseService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.create_user_measurement(&user_id, input).await
     }
 
@@ -225,7 +233,7 @@ impl ExerciseMutation {
         timestamp: DateTimeUtc,
     ) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<ExerciseService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.delete_user_measurement(user_id, timestamp).await
     }
 
@@ -236,7 +244,7 @@ impl ExerciseMutation {
         input: UserWorkoutInput,
     ) -> Result<String> {
         let service = gql_ctx.data_unchecked::<Arc<ExerciseService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.create_user_workout(&user_id, input).await
     }
 
@@ -247,14 +255,14 @@ impl ExerciseMutation {
         input: EditUserWorkoutInput,
     ) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<ExerciseService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.edit_user_workout(user_id, input).await
     }
 
     /// Delete a workout and remove all exercise associations.
     async fn delete_user_workout(&self, gql_ctx: &Context<'_>, workout_id: String) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<ExerciseService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.delete_user_workout(user_id, workout_id).await
     }
 
@@ -265,7 +273,7 @@ impl ExerciseMutation {
         input: exercise::Model,
     ) -> Result<String> {
         let service = gql_ctx.data_unchecked::<Arc<ExerciseService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.create_custom_exercise(user_id, input).await
     }
 
@@ -276,7 +284,7 @@ impl ExerciseMutation {
         input: EditCustomExerciseInput,
     ) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<ExerciseService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.edit_custom_exercise(user_id, input).await
     }
 }
@@ -288,8 +296,6 @@ pub struct ExerciseService {
     perform_application_job: MemoryStorage<ApplicationJob>,
 }
 
-impl AuthProvider for ExerciseService {}
-
 impl ExerciseService {
     pub fn new(
         db: &DatabaseConnection,
@@ -298,8 +304,8 @@ impl ExerciseService {
         perform_application_job: &MemoryStorage<ApplicationJob>,
     ) -> Self {
         Self {
-            db: db.clone(),
             config,
+            db: db.clone(),
             file_storage_service,
             perform_application_job: perform_application_job.clone(),
         }
diff --git a/apps/backend/src/importer/mod.rs b/apps/backend/src/importer/mod.rs
index c58e5aa5f8..180a9dac28 100644
--- a/apps/backend/src/importer/mod.rs
+++ b/apps/backend/src/importer/mod.rs
@@ -181,12 +181,14 @@ pub struct ImportResultResponse {
 #[derive(Default)]
 pub struct ImporterQuery;
 
+impl AuthProvider for ImporterQuery {}
+
 #[Object]
 impl ImporterQuery {
     /// Get all the import jobs deployed by the user.
     async fn import_reports(&self, gql_ctx: &Context<'_>) -> Result<Vec<import_report::Model>> {
         let service = gql_ctx.data_unchecked::<Arc<ImporterService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.import_reports(user_id).await
     }
 }
@@ -194,6 +196,12 @@ impl ImporterQuery {
 #[derive(Default)]
 pub struct ImporterMutation;
 
+impl AuthProvider for ImporterMutation {
+    fn is_mutation(&self) -> bool {
+        true
+    }
+}
+
 #[Object]
 impl ImporterMutation {
     /// Add job to import data from various sources.
@@ -203,7 +211,7 @@ impl ImporterMutation {
         input: DeployImportJobInput,
     ) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<ImporterService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.deploy_import_job(user_id, input).await
     }
 }
@@ -214,8 +222,6 @@ pub struct ImporterService {
     timezone: Arc<chrono_tz::Tz>,
 }
 
-impl AuthProvider for ImporterService {}
-
 impl ImporterService {
     pub fn new(
         media_service: Arc<MiscellaneousService>,
diff --git a/apps/backend/src/jwt.rs b/apps/backend/src/jwt.rs
index 0bf20b9898..0d7d0cbe0e 100644
--- a/apps/backend/src/jwt.rs
+++ b/apps/backend/src/jwt.rs
@@ -2,8 +2,10 @@ use anyhow::Result;
 use chrono::{Duration, Utc};
 use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
 use serde::{Deserialize, Serialize};
+use serde_with::skip_serializing_none;
 use uuid::Uuid;
 
+#[skip_serializing_none]
 #[derive(Debug, Deserialize, Serialize)]
 pub struct Claims {
     pub sub: String,
diff --git a/apps/backend/src/miscellaneous/resolver.rs b/apps/backend/src/miscellaneous/resolver.rs
index be4293f4bc..52e0a17140 100644
--- a/apps/backend/src/miscellaneous/resolver.rs
+++ b/apps/backend/src/miscellaneous/resolver.rs
@@ -651,12 +651,13 @@ struct CreateReviewCommentInput {
 
 #[derive(Debug, Serialize, Deserialize, SimpleObject, Clone, Default)]
 struct GraphqlCalendarEvent {
-    calendar_event_id: String,
     date: NaiveDate,
     metadata_id: String,
     metadata_title: String,
-    metadata_image: Option<String>,
     metadata_lot: MediaLot,
+    calendar_event_id: String,
+    episode_name: Option<String>,
+    metadata_image: Option<String>,
     show_extra_information: Option<SeenShowExtraInformation>,
     podcast_extra_information: Option<SeenPodcastExtraInformation>,
 }
@@ -753,6 +754,8 @@ fn empty_nonce_verifier(_nonce: Option<&Nonce>) -> Result<(), String> {
 #[derive(Default)]
 pub struct MiscellaneousQuery;
 
+impl AuthProvider for MiscellaneousQuery {}
+
 #[Object]
 impl MiscellaneousQuery {
     /// Get some primary information about the service.
@@ -768,7 +771,7 @@ impl MiscellaneousQuery {
         name: Option<String>,
     ) -> Result<Vec<CollectionItem>> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.user_collections_list(&user_id, name).await
     }
 
@@ -839,7 +842,7 @@ impl MiscellaneousQuery {
         input: MetadataListInput,
     ) -> Result<SearchResults<String>> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.metadata_list(user_id, input).await
     }
 
@@ -862,7 +865,7 @@ impl MiscellaneousQuery {
         input: MetadataSearchInput,
     ) -> Result<SearchResults<MetadataSearchItemResponse>> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.metadata_search(&user_id, input).await
     }
 
@@ -883,7 +886,7 @@ impl MiscellaneousQuery {
         input: SearchInput,
     ) -> Result<SearchResults<String>> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.metadata_groups_list(user_id, input).await
     }
 
@@ -899,7 +902,7 @@ impl MiscellaneousQuery {
     /// Get a summary of all the media items that have been consumed by this user.
     async fn latest_user_summary(&self, gql_ctx: &Context<'_>) -> Result<user_summary::Model> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.latest_user_summary(&user_id).await
     }
 
@@ -910,7 +913,7 @@ impl MiscellaneousQuery {
         metadata_group_id: String,
     ) -> Result<UserMetadataGroupDetails> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service
             .user_metadata_group_details(user_id, metadata_group_id)
             .await
@@ -919,7 +922,7 @@ impl MiscellaneousQuery {
     /// Get a user's preferences.
     async fn user_preferences(&self, gql_ctx: &Context<'_>) -> Result<UserPreferences> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.user_preferences(&user_id).await
     }
 
@@ -936,14 +939,14 @@ impl MiscellaneousQuery {
     /// Get details about the currently logged in user.
     async fn user_details(&self, gql_ctx: &Context<'_>) -> Result<UserDetailsResult> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let token = service.user_auth_token_from_ctx(gql_ctx)?;
+        let token = self.user_auth_token_from_ctx(gql_ctx)?;
         service.user_details(&token).await
     }
 
     /// Get all the integrations for the currently logged in user.
     async fn user_integrations(&self, gql_ctx: &Context<'_>) -> Result<Vec<integration::Model>> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.user_integrations(&user_id).await
     }
 
@@ -953,7 +956,7 @@ impl MiscellaneousQuery {
         gql_ctx: &Context<'_>,
     ) -> Result<Vec<notification_platform::Model>> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.user_notification_platforms(&user_id).await
     }
 
@@ -964,7 +967,7 @@ impl MiscellaneousQuery {
         metadata_id: String,
     ) -> Result<UserMetadataDetails> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.user_metadata_details(user_id, metadata_id).await
     }
 
@@ -975,7 +978,7 @@ impl MiscellaneousQuery {
         person_id: String,
     ) -> Result<UserPersonDetails> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.user_person_details(user_id, person_id).await
     }
 
@@ -986,7 +989,7 @@ impl MiscellaneousQuery {
         input: UserCalendarEventInput,
     ) -> Result<Vec<GroupedCalendarEvent>> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.user_calendar_events(user_id, input).await
     }
 
@@ -997,7 +1000,7 @@ impl MiscellaneousQuery {
         input: UserUpcomingCalendarEventInput,
     ) -> Result<Vec<GraphqlCalendarEvent>> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.user_upcoming_calendar_events(user_id, input).await
     }
 
@@ -1008,7 +1011,7 @@ impl MiscellaneousQuery {
         input: PeopleListInput,
     ) -> Result<SearchResults<String>> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.people_list(user_id, input).await
     }
 
@@ -1019,7 +1022,7 @@ impl MiscellaneousQuery {
         input: PeopleSearchInput,
     ) -> Result<SearchResults<PeopleSearchItem>> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.people_search(&user_id, input).await
     }
 
@@ -1030,7 +1033,7 @@ impl MiscellaneousQuery {
         input: MetadataGroupSearchInput,
     ) -> Result<SearchResults<MetadataGroupSearchItem>> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.metadata_group_search(&user_id, input).await
     }
 
@@ -1050,6 +1053,12 @@ impl MiscellaneousQuery {
 #[derive(Default)]
 pub struct MiscellaneousMutation;
 
+impl AuthProvider for MiscellaneousMutation {
+    fn is_mutation(&self) -> bool {
+        true
+    }
+}
+
 #[Object]
 impl MiscellaneousMutation {
     /// Create or update a review.
@@ -1059,14 +1068,14 @@ impl MiscellaneousMutation {
         input: PostReviewInput,
     ) -> Result<StringIdObject> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.post_review(&user_id, input).await
     }
 
     /// Delete a review if it belongs to the currently logged in user.
     async fn delete_review(&self, gql_ctx: &Context<'_>, review_id: String) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.delete_review(user_id, review_id).await
     }
 
@@ -1077,7 +1086,7 @@ impl MiscellaneousMutation {
         input: CreateOrUpdateCollectionInput,
     ) -> Result<StringIdObject> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.create_or_update_collection(&user_id, input).await
     }
 
@@ -1088,7 +1097,7 @@ impl MiscellaneousMutation {
         input: ChangeCollectionToEntityInput,
     ) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.add_entity_to_collection(&user_id, input).await
     }
 
@@ -1099,7 +1108,7 @@ impl MiscellaneousMutation {
         input: ChangeCollectionToEntityInput,
     ) -> Result<StringIdObject> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.remove_entity_from_collection(&user_id, input).await
     }
 
@@ -1110,7 +1119,7 @@ impl MiscellaneousMutation {
         collection_name: String,
     ) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.delete_collection(user_id, &collection_name).await
     }
 
@@ -1121,7 +1130,7 @@ impl MiscellaneousMutation {
         seen_id: String,
     ) -> Result<StringIdObject> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.delete_seen_item(&user_id, seen_id).await
     }
 
@@ -1132,7 +1141,7 @@ impl MiscellaneousMutation {
         input: CreateCustomMetadataInput,
     ) -> Result<StringIdObject> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service
             .create_custom_metadata(user_id, input)
             .await
@@ -1147,7 +1156,7 @@ impl MiscellaneousMutation {
         input: Vec<ProgressUpdateInput>,
     ) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.deploy_bulk_progress_update(user_id, input).await
     }
 
@@ -1180,7 +1189,7 @@ impl MiscellaneousMutation {
         merge_into: String,
     ) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service
             .merge_metadata(user_id, merge_from, merge_into)
             .await
@@ -1243,7 +1252,7 @@ impl MiscellaneousMutation {
         input: UpdateUserInput,
     ) -> Result<StringIdObject> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.update_user(user_id, input).await
     }
 
@@ -1254,7 +1263,7 @@ impl MiscellaneousMutation {
         input: UpdateUserPreferenceInput,
     ) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.update_user_preference(user_id, input).await
     }
 
@@ -1265,7 +1274,7 @@ impl MiscellaneousMutation {
         input: CreateIntegrationInput,
     ) -> Result<StringIdObject> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.create_user_integration(user_id, input).await
     }
 
@@ -1276,7 +1285,7 @@ impl MiscellaneousMutation {
         integration_id: String,
     ) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service
             .delete_user_integration(user_id, integration_id)
             .await
@@ -1289,7 +1298,7 @@ impl MiscellaneousMutation {
         input: CreateUserNotificationPlatformInput,
     ) -> Result<String> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service
             .create_user_notification_platform(user_id, input)
             .await
@@ -1298,7 +1307,7 @@ impl MiscellaneousMutation {
     /// Test all notification platforms for the currently logged in user.
     async fn test_user_notification_platforms(&self, gql_ctx: &Context<'_>) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.test_user_notification_platforms(&user_id).await
     }
 
@@ -1309,7 +1318,7 @@ impl MiscellaneousMutation {
         notification_id: String,
     ) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service
             .delete_user_notification_platform(user_id, notification_id)
             .await
@@ -1318,7 +1327,7 @@ impl MiscellaneousMutation {
     /// Delete a user. The account making the user must an `Admin`.
     async fn delete_user(&self, gql_ctx: &Context<'_>, to_delete_user_id: String) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.admin_account_guard(&user_id).await?;
         service.delete_user(to_delete_user_id).await
     }
@@ -1347,7 +1356,7 @@ impl MiscellaneousMutation {
     /// Generate an auth token without any expiry.
     async fn generate_auth_token(&self, gql_ctx: &Context<'_>) -> Result<String> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.generate_auth_token(user_id).await
     }
 
@@ -1358,7 +1367,7 @@ impl MiscellaneousMutation {
         input: CreateReviewCommentInput,
     ) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.create_review_comment(user_id, input).await
     }
 
@@ -1369,7 +1378,7 @@ impl MiscellaneousMutation {
         input: EditSeenItemInput,
     ) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.edit_seen_item(user_id, input).await
     }
 
@@ -1380,7 +1389,7 @@ impl MiscellaneousMutation {
         job_name: BackgroundJob,
     ) -> Result<bool> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
-        let user_id = service.user_id_from_ctx(gql_ctx).await?;
+        let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.deploy_background_job(&user_id, job_name).await
     }
 
@@ -1404,8 +1413,6 @@ pub struct MiscellaneousService {
     seen_progress_cache: DiskCache<ProgressUpdateCache, ()>,
 }
 
-impl AuthProvider for MiscellaneousService {}
-
 impl MiscellaneousService {
     pub async fn new(
         db: &DatabaseConnection,
@@ -1736,7 +1743,7 @@ impl MiscellaneousService {
         user_id: String,
         metadata_id: String,
     ) -> Result<UserMetadataDetails> {
-        let media_details = self.metadata_details(&metadata_id).await?;
+        let media_details = self.generic_metadata(&metadata_id).await?;
         let collections = entity_in_collections(
             &self.db,
             &user_id,
@@ -1749,70 +1756,76 @@ impl MiscellaneousService {
         let reviews = self
             .item_reviews(&user_id, Some(metadata_id.clone()), None, None, None)
             .await?;
-        let history = self.seen_history(&user_id, &metadata_id).await?;
+        let (is_finished, history) = self
+            .is_metadata_finished_by_user(&user_id, &media_details)
+            .await?;
         let in_progress = history
             .iter()
             .find(|h| h.state == SeenState::InProgress || h.state == SeenState::OnAHold)
             .cloned();
-        let next_entry = history.first().and_then(|h| {
-            if let Some(s) = &media_details.show_specifics {
-                let all_episodes = s
-                    .seasons
-                    .iter()
-                    .map(|s| (s.season_number, &s.episodes))
-                    .collect_vec()
-                    .into_iter()
-                    .flat_map(|(s, e)| {
-                        e.iter().map(move |e| UserMediaNextEntry {
-                            season: Some(s),
-                            episode: Some(e.episode_number),
-                            chapter: None,
+        let next_entry = if is_finished {
+            None
+        } else {
+            history.first().and_then(|h| {
+                if let Some(s) = &media_details.model.show_specifics {
+                    let all_episodes = s
+                        .seasons
+                        .iter()
+                        .map(|s| (s.season_number, &s.episodes))
+                        .collect_vec()
+                        .into_iter()
+                        .flat_map(|(s, e)| {
+                            e.iter().map(move |e| UserMediaNextEntry {
+                                season: Some(s),
+                                episode: Some(e.episode_number),
+                                chapter: None,
+                            })
                         })
-                    })
-                    .collect_vec();
-                let next = all_episodes.iter().position(|e| {
-                    e.season == Some(h.show_extra_information.as_ref().unwrap().season)
-                        && e.episode == Some(h.show_extra_information.as_ref().unwrap().episode)
-                });
-                Some(all_episodes.get(next? + 1)?.clone())
-            } else if let Some(p) = &media_details.podcast_specifics {
-                let all_episodes = p
-                    .episodes
-                    .iter()
-                    .map(|e| UserMediaNextEntry {
-                        season: None,
-                        episode: Some(e.number),
-                        chapter: None,
-                    })
-                    .collect_vec();
-                let next = all_episodes.iter().position(|e| {
-                    e.episode == Some(h.podcast_extra_information.as_ref().unwrap().episode)
-                });
-                Some(all_episodes.get(next? + 1)?.clone())
-            } else if let Some(anime_spec) = &media_details.anime_specifics {
-                anime_spec.episodes.and_then(|_| {
-                    h.anime_extra_information.as_ref().and_then(|hist| {
-                        hist.episode.map(|e| UserMediaNextEntry {
+                        .collect_vec();
+                    let next = all_episodes.iter().position(|e| {
+                        e.season == Some(h.show_extra_information.as_ref().unwrap().season)
+                            && e.episode == Some(h.show_extra_information.as_ref().unwrap().episode)
+                    });
+                    Some(all_episodes.get(next? + 1)?.clone())
+                } else if let Some(p) = &media_details.model.podcast_specifics {
+                    let all_episodes = p
+                        .episodes
+                        .iter()
+                        .map(|e| UserMediaNextEntry {
                             season: None,
-                            episode: Some(e + 1),
+                            episode: Some(e.number),
                             chapter: None,
                         })
+                        .collect_vec();
+                    let next = all_episodes.iter().position(|e| {
+                        e.episode == Some(h.podcast_extra_information.as_ref().unwrap().episode)
+                    });
+                    Some(all_episodes.get(next? + 1)?.clone())
+                } else if let Some(anime_spec) = &media_details.model.anime_specifics {
+                    anime_spec.episodes.and_then(|_| {
+                        h.anime_extra_information.as_ref().and_then(|hist| {
+                            hist.episode.map(|e| UserMediaNextEntry {
+                                season: None,
+                                episode: Some(e + 1),
+                                chapter: None,
+                            })
+                        })
                     })
-                })
-            } else if let Some(manga_spec) = &media_details.manga_specifics {
-                manga_spec.chapters.and_then(|_| {
-                    h.manga_extra_information.as_ref().and_then(|hist| {
-                        hist.chapter.map(|e| UserMediaNextEntry {
-                            season: None,
-                            episode: None,
-                            chapter: Some(e + 1),
+                } else if let Some(manga_spec) = &media_details.model.manga_specifics {
+                    manga_spec.chapters.and_then(|_| {
+                        h.manga_extra_information.as_ref().and_then(|hist| {
+                            hist.chapter.map(|e| UserMediaNextEntry {
+                                season: None,
+                                episode: None,
+                                chapter: Some(e + 1),
+                            })
                         })
                     })
-                })
-            } else {
-                None
-            }
-        });
+                } else {
+                    None
+                }
+            })
+        };
         let metadata_alias = Alias::new("m");
         let seen_alias = Alias::new("s");
         let seen_select = Query::select()
@@ -1859,7 +1872,7 @@ impl MiscellaneousService {
             }
         };
         let seen_by_user_count = history.len();
-        let show_progress = if let Some(show_specifics) = media_details.show_specifics {
+        let show_progress = if let Some(show_specifics) = media_details.model.show_specifics {
             let mut seasons = vec![];
             for season in show_specifics.seasons {
                 let mut episodes = vec![];
@@ -1893,26 +1906,27 @@ impl MiscellaneousService {
         } else {
             None
         };
-        let podcast_progress = if let Some(podcast_specifics) = media_details.podcast_specifics {
-            let mut episodes = vec![];
-            for episode in podcast_specifics.episodes {
-                let seen = history
-                    .iter()
-                    .filter(|h| {
-                        h.podcast_extra_information
-                            .as_ref()
-                            .map_or(false, |s| s.episode == episode.number)
+        let podcast_progress =
+            if let Some(podcast_specifics) = media_details.model.podcast_specifics {
+                let mut episodes = vec![];
+                for episode in podcast_specifics.episodes {
+                    let seen = history
+                        .iter()
+                        .filter(|h| {
+                            h.podcast_extra_information
+                                .as_ref()
+                                .map_or(false, |s| s.episode == episode.number)
+                        })
+                        .collect_vec();
+                    episodes.push(UserMetadataDetailsEpisodeProgress {
+                        episode_number: episode.number,
+                        times_seen: seen.len(),
                     })
-                    .collect_vec();
-                episodes.push(UserMetadataDetailsEpisodeProgress {
-                    episode_number: episode.number,
-                    times_seen: seen.len(),
-                })
-            }
-            Some(episodes)
-        } else {
-            None
-        };
+                }
+                Some(episodes)
+            } else {
+                None
+            };
         Ok(UserMetadataDetails {
             media_reason: user_to_meta.and_then(|n| n.media_reason),
             collections,
@@ -1979,15 +1993,15 @@ impl MiscellaneousService {
         #[derive(Debug, FromQueryResult, Clone)]
         struct CalEvent {
             id: String,
+            m_lot: MediaLot,
             date: NaiveDate,
-            metadata_id: String,
-            metadata_show_extra_information: Option<SeenShowExtraInformation>,
-            metadata_podcast_extra_information: Option<SeenPodcastExtraInformation>,
             m_title: String,
+            metadata_id: String,
             m_images: Option<Vec<MetadataImage>>,
-            m_lot: MediaLot,
             m_show_specifics: Option<ShowSpecifics>,
             m_podcast_specifics: Option<PodcastSpecifics>,
+            metadata_show_extra_information: Option<SeenShowExtraInformation>,
+            metadata_podcast_extra_information: Option<SeenPodcastExtraInformation>,
         }
         let all_events = CalendarEvent::find()
             .column_as(
@@ -2051,11 +2065,13 @@ impl MiscellaneousService {
                 ..Default::default()
             };
             let mut image = None;
+            let mut title = None;
 
             if let Some(s) = evt.metadata_show_extra_information {
                 if let Some(sh) = evt.m_show_specifics {
                     if let Some((_, ep)) = sh.get_episode(s.season, s.episode) {
                         image = ep.poster_images.first().cloned();
+                        title = Some(ep.name.clone());
                     }
                 }
                 calc.show_extra_information = Some(s);
@@ -2063,6 +2079,7 @@ impl MiscellaneousService {
                 if let Some(po) = evt.m_podcast_specifics {
                     if let Some(ep) = po.get_episode(p.episode) {
                         image = ep.thumbnail.clone();
+                        title = Some(ep.title.clone());
                     }
                 };
                 calc.podcast_extra_information = Some(p);
@@ -2072,6 +2089,7 @@ impl MiscellaneousService {
                 image = evt.m_images.first_as_url(&self.file_storage_service).await
             }
             calc.metadata_image = image;
+            calc.episode_name = title;
             events.push(calc);
         }
         Ok(events)
@@ -2618,14 +2636,13 @@ impl MiscellaneousService {
             for ute in all_user_to_entities {
                 let mut new_reasons = HashSet::new();
                 if let Some(metadata_id) = ute.metadata_id.clone() {
-                    let seen_history = self.seen_history(&ute.user_id, &metadata_id).await?;
+                    let metadata = self.generic_metadata(&metadata_id).await?;
+                    let (is_finished, seen_history) = self
+                        .is_metadata_finished_by_user(&ute.user_id, &metadata)
+                        .await?;
                     if !seen_history.is_empty() {
                         new_reasons.insert(UserToMediaReason::Seen);
                     }
-                    let metadata = self.generic_metadata(&metadata_id).await?;
-                    let is_finished = self
-                        .is_metadata_finished_by_user(&ute.user_id, metadata)
-                        .await?;
                     if is_finished {
                         new_reasons.insert(UserToMediaReason::Finished);
                     }
@@ -5854,8 +5871,8 @@ impl MiscellaneousService {
                     || metadata.model.lot == MediaLot::Anime
                     || metadata.model.lot == MediaLot::Manga
                 {
-                    let is_complete = self
-                        .is_metadata_finished_by_user(&seen.user_id, metadata)
+                    let (is_complete, _) = self
+                        .is_metadata_finished_by_user(&seen.user_id, &metadata)
                         .await?;
                     if is_complete {
                         remove_entity_from_collection(&DefaultCollection::InProgress.to_string())
@@ -5885,8 +5902,9 @@ impl MiscellaneousService {
     async fn is_metadata_finished_by_user(
         &self,
         user_id: &String,
-        metadata: MetadataBaseData,
-    ) -> Result<bool> {
+        metadata: &MetadataBaseData,
+    ) -> Result<(bool, Vec<seen::Model>)> {
+        let metadata = metadata.clone();
         let seen_history = self.seen_history(user_id, &metadata.model.id).await?;
         let is_finished = if metadata.model.lot == MediaLot::Podcast
             || metadata.model.lot == MediaLot::Show
@@ -5918,11 +5936,12 @@ impl MiscellaneousService {
                 vec![]
             };
             if all_episodes.is_empty() {
-                return Ok(true);
+                return Ok((true, seen_history));
             }
             let mut bag =
                 HashMap::<String, i32>::from_iter(all_episodes.iter().cloned().map(|e| (e, 0)));
             seen_history
+                .clone()
                 .into_iter()
                 .map(|h| {
                     if let Some(s) = h.show_extra_information {
@@ -5944,11 +5963,9 @@ impl MiscellaneousService {
             let is_complete = values.iter().min() == values.iter().max();
             is_complete
         } else {
-            seen_history
-                .into_iter()
-                .any(|h| h.state == SeenState::Completed)
+            seen_history.iter().any(|h| h.state == SeenState::Completed)
         };
-        Ok(is_finished)
+        Ok((is_finished, seen_history))
     }
 
     async fn queue_notifications_to_user_platforms(
diff --git a/apps/backend/src/traits.rs b/apps/backend/src/traits.rs
index 384762d9c2..12d677dc77 100644
--- a/apps/backend/src/traits.rs
+++ b/apps/backend/src/traits.rs
@@ -106,16 +106,21 @@ pub trait DatabaseAssetsAsUrls {
 
 #[async_trait]
 pub trait AuthProvider {
+    fn is_mutation(&self) -> bool {
+        false
+    }
+
     fn user_auth_token_from_ctx(&self, ctx: &Context<'_>) -> GraphqlResult<String> {
-        let ctx = ctx.data_unchecked::<AuthContext>();
-        ctx.auth_token
+        let auth_ctx = ctx.data_unchecked::<AuthContext>();
+        auth_ctx
+            .auth_token
             .clone()
             .ok_or_else(|| Error::new("The auth token is not present".to_owned()))
     }
 
     async fn user_id_from_ctx(&self, ctx: &Context<'_>) -> GraphqlResult<String> {
-        let ctx = ctx.data_unchecked::<AuthContext>();
-        if let Some(id) = ctx.user_id.to_owned() {
+        let auth_ctx = ctx.data_unchecked::<AuthContext>();
+        if let Some(id) = auth_ctx.user_id.to_owned() {
             Ok(id)
         } else {
             Err(Error::new("User was not logged in"))
diff --git a/apps/frontend/app/components/media.tsx b/apps/frontend/app/components/media.tsx
index bfad700d19..6e20d16d2a 100644
--- a/apps/frontend/app/components/media.tsx
+++ b/apps/frontend/app/components/media.tsx
@@ -421,6 +421,7 @@ const blackBgStyles = {
 export const BaseMediaDisplayItem = (props: {
 	isLoading: boolean;
 	name?: string;
+	altName?: string;
 	imageUrl?: string | null;
 	imageOverlay?: {
 		topRight?: ReactNode;
@@ -511,7 +512,7 @@ export const BaseMediaDisplayItem = (props: {
 					</Flex>
 					<Flex justify="space-between" align="center" mb="xs">
 						<Text w="100%" truncate fw="bold">
-							{props.name}
+							{props.altName ?? props.name}
 						</Text>
 						{props.nameRight}
 					</Flex>
@@ -523,6 +524,8 @@ export const BaseMediaDisplayItem = (props: {
 
 export const MetadataDisplayItem = (props: {
 	metadataId: string;
+	name?: string;
+	altName?: string;
 	topRight?: ReactNode;
 	rightLabel?: ReactNode;
 	rightLabelHistory?: boolean;
@@ -546,8 +549,8 @@ export const MetadataDisplayItem = (props: {
 		idx: number,
 		data: readonly [UserToMediaReason, ReactNode],
 	) => (
-		<Tooltip label={changeCase(data[0])}>
-			<ThemeIcon variant="transparent" size="sm" color="cyan" key={idx}>
+		<Tooltip label={changeCase(data[0])} key={idx}>
+			<ThemeIcon variant="transparent" size="sm" color="cyan">
 				{data[1]}
 			</ThemeIcon>
 		</Tooltip>
@@ -562,7 +565,8 @@ export const MetadataDisplayItem = (props: {
 
 	return (
 		<BaseMediaDisplayItem
-			name={metadataDetails?.title}
+			name={props.name ?? metadataDetails?.title}
+			altName={props.altName}
 			isLoading={isMetadataDetailsLoading}
 			onImageClickBehavior={$path("/media/item/:id", { id: props.metadataId })}
 			imageUrl={metadataDetails?.image}
diff --git a/apps/frontend/app/root.tsx b/apps/frontend/app/root.tsx
index 2c4bb81fe8..b6e250df9d 100644
--- a/apps/frontend/app/root.tsx
+++ b/apps/frontend/app/root.tsx
@@ -1,4 +1,3 @@
-import { $path } from "@ignisda/remix-routes";
 import {
 	ActionIcon,
 	Alert,
@@ -15,7 +14,6 @@ import {
 	type MetaFunction,
 	unstable_defineLoader,
 } from "@remix-run/node";
-import { Form } from "@remix-run/react";
 import {
 	Links,
 	Meta,
@@ -33,7 +31,7 @@ import { Toaster } from "~/components/toaster";
 import { LOGO_IMAGE_URL, queryClient } from "~/lib/generals";
 import {
 	colorSchemeCookie,
-	combineHeaders,
+	extendResponseHeaders,
 	getToast,
 } from "~/lib/utilities.server";
 
@@ -93,16 +91,15 @@ export const links: LinksFunction = () => {
 	];
 };
 
-export const loader = unstable_defineLoader(async ({ request }) => {
+export const loader = unstable_defineLoader(async ({ request, response }) => {
 	const { toast, headers: toastHeaders } = await getToast(request);
 	const colorScheme = await colorSchemeCookie.parse(
 		request.headers.get("cookie") || "",
 	);
 	const defaultColorScheme = colorScheme || "light";
-	return Response.json(
-		{ toast, defaultColorScheme },
-		{ headers: combineHeaders(toastHeaders) },
-	);
+	if (toastHeaders)
+		response.headers = extendResponseHeaders(response.headers, toastHeaders);
+	return { toast, defaultColorScheme };
 });
 
 const DefaultHeadTags = () => {
@@ -162,38 +159,3 @@ export default function App() {
 		</html>
 	);
 }
-
-export function ErrorBoundary() {
-	return (
-		<html lang="en">
-			<head>
-				<DefaultHeadTags />
-				<Meta />
-				<Links />
-			</head>
-			<body>
-				<div>
-					We encountered an error. If you recently upgraded the server, you may
-					have to logout and login again. If the error still persists, please
-					create a new issue on{" "}
-					<a
-						href="https://github.com/ignisda/ryot/issues"
-						target="_blank"
-						rel="noreferrer noopener"
-					>
-						GitHub
-					</a>
-					.
-				</div>
-				<Form
-					replace
-					method="POST"
-					action={$path("/actions", { intent: "logout" })}
-				>
-					<button type="submit">Logout</button>
-				</Form>
-				<Scripts />
-			</body>
-		</html>
-	);
-}
diff --git a/apps/frontend/app/routes/_dashboard._index.tsx b/apps/frontend/app/routes/_dashboard._index.tsx
index 59132edd37..c49ce526dd 100644
--- a/apps/frontend/app/routes/_dashboard._index.tsx
+++ b/apps/frontend/app/routes/_dashboard._index.tsx
@@ -464,6 +464,7 @@ const UpComingMedia = ({ um }: { um: CalendarEventPartFragment }) => {
 
 	return (
 		<MetadataDisplayItem
+			altName={um.episodeName || um.metadataTitle}
 			metadataId={um.metadataId}
 			noLeftLabel
 			rightLabel={`${match(um.metadataLot)
diff --git a/apps/frontend/app/routes/_dashboard.calendar.tsx b/apps/frontend/app/routes/_dashboard.calendar.tsx
index d8493d33dc..3d65305fb3 100644
--- a/apps/frontend/app/routes/_dashboard.calendar.tsx
+++ b/apps/frontend/app/routes/_dashboard.calendar.tsx
@@ -10,6 +10,7 @@ import {
 	Stack,
 	Text,
 	Title,
+	Tooltip,
 } from "@mantine/core";
 import { unstable_defineLoader } from "@remix-run/node";
 import {
@@ -21,7 +22,7 @@ import {
 	UserCalendarEventsDocument,
 	type UserCalendarEventsQuery,
 } from "@ryot/generated/graphql/backend/graphql";
-import { isNumber, snakeCase, startCase, sum } from "@ryot/ts-utils";
+import { isNumber, snakeCase, startCase, sum, truncate } from "@ryot/ts-utils";
 import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react";
 import { z } from "zod";
 import { zx } from "zodix";
@@ -140,14 +141,16 @@ const CalendarEvent = (props: {
 					align="end"
 				>
 					<Text mt="sm" size="sm">
-						<Anchor
-							component={Link}
-							to={$path("/media/item/:id", {
-								id: evt.metadataId,
-							})}
-						>
-							{evt.metadataTitle}
-						</Anchor>{" "}
+						<Tooltip label={evt.metadataTitle} disabled={!evt.episodeName}>
+							<Anchor
+								component={Link}
+								to={$path("/media/item/:id", {
+									id: evt.metadataId,
+								})}
+							>
+								{truncate(evt.episodeName ?? evt.metadataTitle, { length: 40 })}
+							</Anchor>
+						</Tooltip>{" "}
 						{isNumber(evt.showExtraInformation?.season) ? (
 							<Text span c="dimmed" size="sm">
 								(S{evt.showExtraInformation.season}-E
diff --git a/apps/frontend/app/routes/_dashboard.settings.preferences.tsx b/apps/frontend/app/routes/_dashboard.settings.preferences.tsx
index 95c20ac3de..7361de1666 100644
--- a/apps/frontend/app/routes/_dashboard.settings.preferences.tsx
+++ b/apps/frontend/app/routes/_dashboard.settings.preferences.tsx
@@ -224,7 +224,10 @@ export default function Page() {
 						<Tabs.Tab value="fitness">Fitness</Tabs.Tab>
 					</Tabs.List>
 					<Tabs.Panel value="dashboard">
-						<Text mb="md">The different sections on the dashboard.</Text>
+						<Text mb="md">
+							The different sections on the dashboard. Drag and drop using the
+							handle to re-arrange them.
+						</Text>
 						<DragDropContext
 							onDragEnd={({ destination, source }) => {
 								if (!userDetails.isDemo) {
diff --git a/apps/frontend/app/routes/_dashboard.tsx b/apps/frontend/app/routes/_dashboard.tsx
index 7047cdcb2f..e5391681f6 100644
--- a/apps/frontend/app/routes/_dashboard.tsx
+++ b/apps/frontend/app/routes/_dashboard.tsx
@@ -11,12 +11,15 @@ import {
 	Button,
 	Center,
 	Checkbox,
+	Code,
 	Collapse,
+	Container,
 	Drawer,
 	Flex,
 	Group,
 	Image,
 	Input,
+	List,
 	Loader,
 	Modal,
 	NumberInput,
@@ -54,6 +57,7 @@ import {
 	useLoaderData,
 	useLocation,
 	useNavigate,
+	useRouteError,
 } from "@remix-run/react";
 import {
 	CollectionExtraInformationLot,
@@ -249,6 +253,75 @@ export const loader = unstable_defineLoader(async ({ request }) => {
 	};
 });
 
+export function ErrorBoundary() {
+	const error = useRouteError() as Error;
+
+	return (
+		<Container size="sm" pt={200}>
+			<Stack p={{ base: "sm", md: "xl" }}>
+				<Text c="red" fz={{ base: 30, md: 40 }}>
+					We encountered an error
+				</Text>
+				<Text>This could be due to several reasons:</Text>
+				<List>
+					<List.Item>Your login session has expired/revoked.</List.Item>
+					<List.Item>You don't have access to this resource.</List.Item>
+					<List.Item>
+						A backwards incompatible server change has been made.
+					</List.Item>
+					<List.Item>There was a backend server error.</List.Item>
+				</List>
+				<Text>
+					In most cases, logging out and then logging back in should fix the
+					issue.
+				</Text>
+				<Group wrap="nowrap">
+					<Button
+						fullWidth
+						color="green"
+						variant="outline"
+						onClick={() => window.location.reload()}
+					>
+						Reload
+					</Button>
+					<Form
+						replace
+						method="POST"
+						style={{ width: "100%" }}
+						action={$path("/actions", { intent: "logout" })}
+					>
+						<Button type="submit" variant="outline" color="blue" fullWidth>
+							Logout
+						</Button>
+					</Form>
+				</Group>
+				<Text>
+					If the error still persists please contact the developer on{" "}
+					<Anchor
+						target="_blank"
+						rel="noreferrer noopener"
+						href="https://discord.gg/D9XTg2a7R8"
+					>
+						Discord
+					</Anchor>{" "}
+					or create an issue on{" "}
+					<Anchor
+						target="_blank"
+						rel="noreferrer noopener"
+						href="https://github.com/ignisda/ryot/issues"
+					>
+						Github
+					</Anchor>
+					. Here is the complete error that occurred:
+				</Text>
+				<Code mah={100} c="pink">
+					{error.message}
+				</Code>
+			</Stack>
+		</Container>
+	);
+}
+
 export default function Layout() {
 	const loaderData = useLoaderData<typeof loader>();
 	const [parent] = useAutoAnimate();
diff --git a/docs/content/guides/exporting.md b/docs/content/guides/exporting.md
index 66b10182ad..03ef903159 100644
--- a/docs/content/guides/exporting.md
+++ b/docs/content/guides/exporting.md
@@ -35,17 +35,17 @@ S3 instance offered by [Minio](https://play.min.io).
     ```
 - Restart your Ryot instance and follow the steps described in the previous section.
 
-## Type definition
-
-```ts
-{% include 'export-schema.ts' %}
-```
-
 ## Exporting the entire database
 
-While debugging bugs, I might ask you to send me a database dump. You can do this by
-exporting the entire database and emailing the file.
+While debugging, I might ask you to send me a database dump. You can do this by exporting
+the entire database and emailing the file.
 
 ```bash
 docker exec -u postgres -i ryot-db pg_dump -Fc --no-acl --no-owner > /tmp/ryot.file.sql
 ```
+
+## Type definition
+
+```ts
+{% include 'export-schema.ts' %}
+```
diff --git a/docs/content/index.md b/docs/content/index.md
index e2db27378d..3adf59cbdd 100644
--- a/docs/content/index.md
+++ b/docs/content/index.md
@@ -35,7 +35,7 @@ volumes:
 !!! info
 
     The pro version is not backwards compatible with the community version, so be sure to
-    backup your data before upgrading.
+    [backup](./guides/exporting.md#exporting-the-entire-database) your data before upgrading.
 
 To upgrade to the pro version, you need to provide a `SERVER_PRO_KEY` environment variable.
 You can get a key by contacting us via the [website]({{ extra.main_website_url }}).
diff --git a/libs/config/Cargo.toml b/libs/config/Cargo.toml
index 818a67f64b..6f60e4fb2c 100644
--- a/libs/config/Cargo.toml
+++ b/libs/config/Cargo.toml
@@ -5,6 +5,7 @@ edition = "2021"
 
 [dependencies]
 anyhow = { workspace = true }
+dotenvy_macro = { workspace = true }
 rs-utils = { path = "../../libs/rs-utils" }
 schematic = { workspace = true }
 serde = { workspace = true }
diff --git a/libs/config/src/lib.rs b/libs/config/src/lib.rs
index 3a25d4ee9d..9628440a64 100644
--- a/libs/config/src/lib.rs
+++ b/libs/config/src/lib.rs
@@ -5,12 +5,22 @@ use rs_utils::{IsFeatureEnabled, PROJECT_NAME};
 use schematic::{derive_enum, validate::not_empty, Config, ConfigEnum, ConfigLoader, HandlerError};
 use serde::{Deserialize, Serialize};
 
+#[cfg(debug_assertions)]
+pub const DEFAULT_TMDB_ACCESS_TOKEN: &str = dotenvy_macro::dotenv!("DEFAULT_TMDB_ACCESS_TOKEN");
+#[cfg(not(debug_assertions))]
+pub const DEFAULT_TMDB_ACCESS_TOKEN: &str = env!("DEFAULT_TMDB_ACCESS_TOKEN");
+
 fn default_tmdb_access_token(_ctx: &()) -> Result<Option<String>, HandlerError> {
-    Ok(Some("eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI4ZGVlOTZjMjc0OGVhY2U0NzU2MGJkMWU4YzE5NTljMCIsInN1YiI6IjY0NDRiYmE4MmM2YjdiMDRiZTdlZDJmNSIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.ZZZNJMXStvAOPJlT0hOBVPSTppFAK3mcUpmbJsExIq4".to_owned()))
+    Ok(Some(DEFAULT_TMDB_ACCESS_TOKEN.to_owned()))
 }
 
+#[cfg(debug_assertions)]
+pub const DEFAULT_MAL_CLIENT_ID: &str = dotenvy_macro::dotenv!("DEFAULT_MAL_CLIENT_ID");
+#[cfg(not(debug_assertions))]
+pub const DEFAULT_MAL_CLIENT_ID: &str = env!("DEFAULT_MAL_CLIENT_ID");
+
 fn default_mal_client_id(_ctx: &()) -> Result<Option<String>, HandlerError> {
-    Ok(Some("3879694bbe52ac3204be9ff68af8f027".to_owned()))
+    Ok(Some(DEFAULT_MAL_CLIENT_ID.to_owned()))
 }
 
 #[derive(Debug, Serialize, Deserialize, Clone, Config)]
diff --git a/libs/generated/src/graphql/backend/gql.ts b/libs/generated/src/graphql/backend/gql.ts
index 493eb8507e..05a28d77f8 100644
--- a/libs/generated/src/graphql/backend/gql.ts
+++ b/libs/generated/src/graphql/backend/gql.ts
@@ -42,7 +42,7 @@ const documents = {
     "query UserWorkoutList($input: SearchInput!) {\n  userWorkoutList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      startTime\n      endTime\n      summary {\n        ...WorkoutSummaryPart\n      }\n    }\n  }\n}": types.UserWorkoutListDocument,
     "query WorkoutDetails($workoutId: String!) {\n  workoutDetails(workoutId: $workoutId) {\n    id\n    name\n    comment\n    startTime\n    endTime\n    repeatedFrom\n    summary {\n      ...WorkoutSummaryPart\n    }\n    information {\n      assets {\n        ...EntityAssetsPart\n      }\n      exercises {\n        name\n        lot\n        notes\n        restTime\n        total {\n          ...WorkoutOrExerciseTotalsPart\n        }\n        supersetWith\n        assets {\n          ...EntityAssetsPart\n        }\n        sets {\n          statistic {\n            ...WorkoutSetStatisticPart\n          }\n          note\n          lot\n          personalBests\n          confirmedAt\n        }\n      }\n    }\n  }\n}": types.WorkoutDetailsDocument,
     "query CoreEnabledFeatures {\n  coreEnabledFeatures {\n    fileStorage\n    signupAllowed\n  }\n}\n\nquery GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery ProvidersLanguageInformation {\n  providersLanguageInformation {\n    supported\n    default\n    source\n  }\n}\n\nquery UserExports {\n  userExports {\n    startedAt\n    endedAt\n    url\n    exported\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    source\n    createdOn\n    lastTriggeredOn\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    name\n    lot\n  }\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: SearchInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}": types.CoreEnabledFeaturesDocument,
-    "fragment SeenPodcastExtraInformationPart on SeenPodcastExtraInformation {\n  episode\n}\n\nfragment SeenShowExtraInformationPart on SeenShowExtraInformation {\n  episode\n  season\n}\n\nfragment SeenAnimeExtraInformationPart on SeenAnimeExtraInformation {\n  episode\n}\n\nfragment SeenMangaExtraInformationPart on SeenMangaExtraInformation {\n  chapter\n  volume\n}\n\nfragment CalendarEventPart on GraphqlCalendarEvent {\n  calendarEventId\n  metadataId\n  metadataTitle\n  metadataLot\n  metadataImage\n  date\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n}\n\nfragment SeenPart on Seen {\n  id\n  progress\n  providerWatchedOn\n  state\n  startedOn\n  finishedOn\n  lastUpdatedOn\n  numTimesUpdated\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}\n\nfragment MetadataSearchItemPart on MetadataSearchItem {\n  identifier\n  title\n  image\n  publishYear\n}\n\nfragment WorkoutOrExerciseTotalsPart on WorkoutOrExerciseTotals {\n  personalBestsAchieved\n  weight\n  reps\n  distance\n  duration\n  restTime\n}\n\nfragment EntityAssetsPart on EntityAssets {\n  images\n  videos\n}\n\nfragment WorkoutSetStatisticPart on WorkoutSetStatistic {\n  duration\n  distance\n  reps\n  weight\n  oneRm\n  pace\n  volume\n}\n\nfragment WorkoutSummaryPart on WorkoutSummary {\n  total {\n    ...WorkoutOrExerciseTotalsPart\n  }\n  exercises {\n    numSets\n    id\n    lot\n    bestSet {\n      statistic {\n        ...WorkoutSetStatisticPart\n      }\n      lot\n      personalBests\n    }\n  }\n}\n\nfragment CollectionPart on Collection {\n  id\n  name\n  userId\n}\n\nfragment ReviewItemPart on ReviewItem {\n  id\n  rating\n  textOriginal\n  textRendered\n  isSpoiler\n  visibility\n  postedOn\n  postedBy {\n    id\n    name\n  }\n  comments {\n    id\n    text\n    createdOn\n    user {\n      id\n      name\n    }\n    likedBy\n  }\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}": types.SeenPodcastExtraInformationPartFragmentDoc,
+    "fragment SeenPodcastExtraInformationPart on SeenPodcastExtraInformation {\n  episode\n}\n\nfragment SeenShowExtraInformationPart on SeenShowExtraInformation {\n  episode\n  season\n}\n\nfragment SeenAnimeExtraInformationPart on SeenAnimeExtraInformation {\n  episode\n}\n\nfragment SeenMangaExtraInformationPart on SeenMangaExtraInformation {\n  chapter\n  volume\n}\n\nfragment CalendarEventPart on GraphqlCalendarEvent {\n  date\n  metadataId\n  metadataLot\n  episodeName\n  metadataTitle\n  metadataImage\n  calendarEventId\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n}\n\nfragment SeenPart on Seen {\n  id\n  progress\n  providerWatchedOn\n  state\n  startedOn\n  finishedOn\n  lastUpdatedOn\n  numTimesUpdated\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}\n\nfragment MetadataSearchItemPart on MetadataSearchItem {\n  identifier\n  title\n  image\n  publishYear\n}\n\nfragment WorkoutOrExerciseTotalsPart on WorkoutOrExerciseTotals {\n  personalBestsAchieved\n  weight\n  reps\n  distance\n  duration\n  restTime\n}\n\nfragment EntityAssetsPart on EntityAssets {\n  images\n  videos\n}\n\nfragment WorkoutSetStatisticPart on WorkoutSetStatistic {\n  duration\n  distance\n  reps\n  weight\n  oneRm\n  pace\n  volume\n}\n\nfragment WorkoutSummaryPart on WorkoutSummary {\n  total {\n    ...WorkoutOrExerciseTotalsPart\n  }\n  exercises {\n    numSets\n    id\n    lot\n    bestSet {\n      statistic {\n        ...WorkoutSetStatisticPart\n      }\n      lot\n      personalBests\n    }\n  }\n}\n\nfragment CollectionPart on Collection {\n  id\n  name\n  userId\n}\n\nfragment ReviewItemPart on ReviewItem {\n  id\n  rating\n  textOriginal\n  textRendered\n  isSpoiler\n  visibility\n  postedOn\n  postedBy {\n    id\n    name\n  }\n  comments {\n    id\n    text\n    createdOn\n    user {\n      id\n      name\n    }\n    likedBy\n  }\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}": types.SeenPodcastExtraInformationPartFragmentDoc,
 };
 
 /**
@@ -178,7 +178,7 @@ export function graphql(source: "query CoreEnabledFeatures {\n  coreEnabledFeatu
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
-export function graphql(source: "fragment SeenPodcastExtraInformationPart on SeenPodcastExtraInformation {\n  episode\n}\n\nfragment SeenShowExtraInformationPart on SeenShowExtraInformation {\n  episode\n  season\n}\n\nfragment SeenAnimeExtraInformationPart on SeenAnimeExtraInformation {\n  episode\n}\n\nfragment SeenMangaExtraInformationPart on SeenMangaExtraInformation {\n  chapter\n  volume\n}\n\nfragment CalendarEventPart on GraphqlCalendarEvent {\n  calendarEventId\n  metadataId\n  metadataTitle\n  metadataLot\n  metadataImage\n  date\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n}\n\nfragment SeenPart on Seen {\n  id\n  progress\n  providerWatchedOn\n  state\n  startedOn\n  finishedOn\n  lastUpdatedOn\n  numTimesUpdated\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}\n\nfragment MetadataSearchItemPart on MetadataSearchItem {\n  identifier\n  title\n  image\n  publishYear\n}\n\nfragment WorkoutOrExerciseTotalsPart on WorkoutOrExerciseTotals {\n  personalBestsAchieved\n  weight\n  reps\n  distance\n  duration\n  restTime\n}\n\nfragment EntityAssetsPart on EntityAssets {\n  images\n  videos\n}\n\nfragment WorkoutSetStatisticPart on WorkoutSetStatistic {\n  duration\n  distance\n  reps\n  weight\n  oneRm\n  pace\n  volume\n}\n\nfragment WorkoutSummaryPart on WorkoutSummary {\n  total {\n    ...WorkoutOrExerciseTotalsPart\n  }\n  exercises {\n    numSets\n    id\n    lot\n    bestSet {\n      statistic {\n        ...WorkoutSetStatisticPart\n      }\n      lot\n      personalBests\n    }\n  }\n}\n\nfragment CollectionPart on Collection {\n  id\n  name\n  userId\n}\n\nfragment ReviewItemPart on ReviewItem {\n  id\n  rating\n  textOriginal\n  textRendered\n  isSpoiler\n  visibility\n  postedOn\n  postedBy {\n    id\n    name\n  }\n  comments {\n    id\n    text\n    createdOn\n    user {\n      id\n      name\n    }\n    likedBy\n  }\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}"): (typeof documents)["fragment SeenPodcastExtraInformationPart on SeenPodcastExtraInformation {\n  episode\n}\n\nfragment SeenShowExtraInformationPart on SeenShowExtraInformation {\n  episode\n  season\n}\n\nfragment SeenAnimeExtraInformationPart on SeenAnimeExtraInformation {\n  episode\n}\n\nfragment SeenMangaExtraInformationPart on SeenMangaExtraInformation {\n  chapter\n  volume\n}\n\nfragment CalendarEventPart on GraphqlCalendarEvent {\n  calendarEventId\n  metadataId\n  metadataTitle\n  metadataLot\n  metadataImage\n  date\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n}\n\nfragment SeenPart on Seen {\n  id\n  progress\n  providerWatchedOn\n  state\n  startedOn\n  finishedOn\n  lastUpdatedOn\n  numTimesUpdated\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}\n\nfragment MetadataSearchItemPart on MetadataSearchItem {\n  identifier\n  title\n  image\n  publishYear\n}\n\nfragment WorkoutOrExerciseTotalsPart on WorkoutOrExerciseTotals {\n  personalBestsAchieved\n  weight\n  reps\n  distance\n  duration\n  restTime\n}\n\nfragment EntityAssetsPart on EntityAssets {\n  images\n  videos\n}\n\nfragment WorkoutSetStatisticPart on WorkoutSetStatistic {\n  duration\n  distance\n  reps\n  weight\n  oneRm\n  pace\n  volume\n}\n\nfragment WorkoutSummaryPart on WorkoutSummary {\n  total {\n    ...WorkoutOrExerciseTotalsPart\n  }\n  exercises {\n    numSets\n    id\n    lot\n    bestSet {\n      statistic {\n        ...WorkoutSetStatisticPart\n      }\n      lot\n      personalBests\n    }\n  }\n}\n\nfragment CollectionPart on Collection {\n  id\n  name\n  userId\n}\n\nfragment ReviewItemPart on ReviewItem {\n  id\n  rating\n  textOriginal\n  textRendered\n  isSpoiler\n  visibility\n  postedOn\n  postedBy {\n    id\n    name\n  }\n  comments {\n    id\n    text\n    createdOn\n    user {\n      id\n      name\n    }\n    likedBy\n  }\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}"];
+export function graphql(source: "fragment SeenPodcastExtraInformationPart on SeenPodcastExtraInformation {\n  episode\n}\n\nfragment SeenShowExtraInformationPart on SeenShowExtraInformation {\n  episode\n  season\n}\n\nfragment SeenAnimeExtraInformationPart on SeenAnimeExtraInformation {\n  episode\n}\n\nfragment SeenMangaExtraInformationPart on SeenMangaExtraInformation {\n  chapter\n  volume\n}\n\nfragment CalendarEventPart on GraphqlCalendarEvent {\n  date\n  metadataId\n  metadataLot\n  episodeName\n  metadataTitle\n  metadataImage\n  calendarEventId\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n}\n\nfragment SeenPart on Seen {\n  id\n  progress\n  providerWatchedOn\n  state\n  startedOn\n  finishedOn\n  lastUpdatedOn\n  numTimesUpdated\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}\n\nfragment MetadataSearchItemPart on MetadataSearchItem {\n  identifier\n  title\n  image\n  publishYear\n}\n\nfragment WorkoutOrExerciseTotalsPart on WorkoutOrExerciseTotals {\n  personalBestsAchieved\n  weight\n  reps\n  distance\n  duration\n  restTime\n}\n\nfragment EntityAssetsPart on EntityAssets {\n  images\n  videos\n}\n\nfragment WorkoutSetStatisticPart on WorkoutSetStatistic {\n  duration\n  distance\n  reps\n  weight\n  oneRm\n  pace\n  volume\n}\n\nfragment WorkoutSummaryPart on WorkoutSummary {\n  total {\n    ...WorkoutOrExerciseTotalsPart\n  }\n  exercises {\n    numSets\n    id\n    lot\n    bestSet {\n      statistic {\n        ...WorkoutSetStatisticPart\n      }\n      lot\n      personalBests\n    }\n  }\n}\n\nfragment CollectionPart on Collection {\n  id\n  name\n  userId\n}\n\nfragment ReviewItemPart on ReviewItem {\n  id\n  rating\n  textOriginal\n  textRendered\n  isSpoiler\n  visibility\n  postedOn\n  postedBy {\n    id\n    name\n  }\n  comments {\n    id\n    text\n    createdOn\n    user {\n      id\n      name\n    }\n    likedBy\n  }\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}"): (typeof documents)["fragment SeenPodcastExtraInformationPart on SeenPodcastExtraInformation {\n  episode\n}\n\nfragment SeenShowExtraInformationPart on SeenShowExtraInformation {\n  episode\n  season\n}\n\nfragment SeenAnimeExtraInformationPart on SeenAnimeExtraInformation {\n  episode\n}\n\nfragment SeenMangaExtraInformationPart on SeenMangaExtraInformation {\n  chapter\n  volume\n}\n\nfragment CalendarEventPart on GraphqlCalendarEvent {\n  date\n  metadataId\n  metadataLot\n  episodeName\n  metadataTitle\n  metadataImage\n  calendarEventId\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n}\n\nfragment SeenPart on Seen {\n  id\n  progress\n  providerWatchedOn\n  state\n  startedOn\n  finishedOn\n  lastUpdatedOn\n  numTimesUpdated\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}\n\nfragment MetadataSearchItemPart on MetadataSearchItem {\n  identifier\n  title\n  image\n  publishYear\n}\n\nfragment WorkoutOrExerciseTotalsPart on WorkoutOrExerciseTotals {\n  personalBestsAchieved\n  weight\n  reps\n  distance\n  duration\n  restTime\n}\n\nfragment EntityAssetsPart on EntityAssets {\n  images\n  videos\n}\n\nfragment WorkoutSetStatisticPart on WorkoutSetStatistic {\n  duration\n  distance\n  reps\n  weight\n  oneRm\n  pace\n  volume\n}\n\nfragment WorkoutSummaryPart on WorkoutSummary {\n  total {\n    ...WorkoutOrExerciseTotalsPart\n  }\n  exercises {\n    numSets\n    id\n    lot\n    bestSet {\n      statistic {\n        ...WorkoutSetStatisticPart\n      }\n      lot\n      personalBests\n    }\n  }\n}\n\nfragment CollectionPart on Collection {\n  id\n  name\n  userId\n}\n\nfragment ReviewItemPart on ReviewItem {\n  id\n  rating\n  textOriginal\n  textRendered\n  isSpoiler\n  visibility\n  postedOn\n  postedBy {\n    id\n    name\n  }\n  comments {\n    id\n    text\n    createdOn\n    user {\n      id\n      name\n    }\n    likedBy\n  }\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}"];
 
 export function graphql(source: string) {
   return (documents as any)[source] ?? {};
diff --git a/libs/generated/src/graphql/backend/graphql.ts b/libs/generated/src/graphql/backend/graphql.ts
index b218f59167..48d16caf99 100644
--- a/libs/generated/src/graphql/backend/graphql.ts
+++ b/libs/generated/src/graphql/backend/graphql.ts
@@ -570,6 +570,7 @@ export type GenreListResults = {
 export type GraphqlCalendarEvent = {
   calendarEventId: Scalars['String']['output'];
   date: Scalars['NaiveDate']['output'];
+  episodeName?: Maybe<Scalars['String']['output']>;
   metadataId: Scalars['String']['output'];
   metadataImage?: Maybe<Scalars['String']['output']>;
   metadataLot: MediaLot;
@@ -2911,14 +2912,14 @@ export type UserUpcomingCalendarEventsQueryVariables = Exact<{
 }>;
 
 
-export type UserUpcomingCalendarEventsQuery = { userUpcomingCalendarEvents: Array<{ calendarEventId: string, metadataId: string, metadataTitle: string, metadataLot: MediaLot, metadataImage?: string | null, date: string, showExtraInformation?: { episode: number, season: number } | null, podcastExtraInformation?: { episode: number } | null }> };
+export type UserUpcomingCalendarEventsQuery = { userUpcomingCalendarEvents: Array<{ date: string, metadataId: string, metadataLot: MediaLot, episodeName?: string | null, metadataTitle: string, metadataImage?: string | null, calendarEventId: string, showExtraInformation?: { episode: number, season: number } | null, podcastExtraInformation?: { episode: number } | null }> };
 
 export type UserCalendarEventsQueryVariables = Exact<{
   input: UserCalendarEventInput;
 }>;
 
 
-export type UserCalendarEventsQuery = { userCalendarEvents: Array<{ date: string, events: Array<{ calendarEventId: string, metadataId: string, metadataTitle: string, metadataLot: MediaLot, metadataImage?: string | null, date: string, showExtraInformation?: { episode: number, season: number } | null, podcastExtraInformation?: { episode: number } | null }> }> };
+export type UserCalendarEventsQuery = { userCalendarEvents: Array<{ date: string, events: Array<{ date: string, metadataId: string, metadataLot: MediaLot, episodeName?: string | null, metadataTitle: string, metadataImage?: string | null, calendarEventId: string, showExtraInformation?: { episode: number, season: number } | null, podcastExtraInformation?: { episode: number } | null }> }> };
 
 export type MetadataPartialDetailsQueryVariables = Exact<{
   metadataId: Scalars['String']['input'];
@@ -2949,7 +2950,7 @@ export type SeenAnimeExtraInformationPartFragment = { episode?: number | null };
 
 export type SeenMangaExtraInformationPartFragment = { chapter?: number | null, volume?: number | null };
 
-export type CalendarEventPartFragment = { calendarEventId: string, metadataId: string, metadataTitle: string, metadataLot: MediaLot, metadataImage?: string | null, date: string, showExtraInformation?: { episode: number, season: number } | null, podcastExtraInformation?: { episode: number } | null };
+export type CalendarEventPartFragment = { date: string, metadataId: string, metadataLot: MediaLot, episodeName?: string | null, metadataTitle: string, metadataImage?: string | null, calendarEventId: string, showExtraInformation?: { episode: number, season: number } | null, podcastExtraInformation?: { episode: number } | null };
 
 export type SeenPartFragment = { id: string, progress: string, providerWatchedOn?: string | null, state: SeenState, startedOn?: string | null, finishedOn?: string | null, lastUpdatedOn: string, numTimesUpdated: number, showExtraInformation?: { episode: number, season: number } | null, podcastExtraInformation?: { episode: number } | null, animeExtraInformation?: { episode?: number | null } | null, mangaExtraInformation?: { chapter?: number | null, volume?: number | null } | null };
 
@@ -2969,7 +2970,7 @@ export type ReviewItemPartFragment = { id: string, rating?: string | null, textO
 
 export const SeenShowExtraInformationPartFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}}]} as unknown as DocumentNode<SeenShowExtraInformationPartFragment, unknown>;
 export const SeenPodcastExtraInformationPartFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}}]} as unknown as DocumentNode<SeenPodcastExtraInformationPartFragment, unknown>;
-export const CalendarEventPartFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CalendarEventPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"GraphqlCalendarEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"calendarEventId"}},{"kind":"Field","name":{"kind":"Name","value":"metadataId"}},{"kind":"Field","name":{"kind":"Name","value":"metadataTitle"}},{"kind":"Field","name":{"kind":"Name","value":"metadataLot"}},{"kind":"Field","name":{"kind":"Name","value":"metadataImage"}},{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}}]} as unknown as DocumentNode<CalendarEventPartFragment, unknown>;
+export const CalendarEventPartFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CalendarEventPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"GraphqlCalendarEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"metadataId"}},{"kind":"Field","name":{"kind":"Name","value":"metadataLot"}},{"kind":"Field","name":{"kind":"Name","value":"episodeName"}},{"kind":"Field","name":{"kind":"Name","value":"metadataTitle"}},{"kind":"Field","name":{"kind":"Name","value":"metadataImage"}},{"kind":"Field","name":{"kind":"Name","value":"calendarEventId"}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}}]} as unknown as DocumentNode<CalendarEventPartFragment, unknown>;
 export const SeenAnimeExtraInformationPartFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenAnimeExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}}]} as unknown as DocumentNode<SeenAnimeExtraInformationPartFragment, unknown>;
 export const SeenMangaExtraInformationPartFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenMangaExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"chapter"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}}]}}]} as unknown as DocumentNode<SeenMangaExtraInformationPartFragment, unknown>;
 export const SeenPartFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Seen"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"progress"}},{"kind":"Field","name":{"kind":"Name","value":"providerWatchedOn"}},{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"startedOn"}},{"kind":"Field","name":{"kind":"Name","value":"finishedOn"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedOn"}},{"kind":"Field","name":{"kind":"Name","value":"numTimesUpdated"}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenAnimeExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenMangaExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"chapter"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}}]}}]} as unknown as DocumentNode<SeenPartFragment, unknown>;
@@ -3055,8 +3056,8 @@ export const UserCollectionsListDocument = {"kind":"Document","definitions":[{"k
 export const UserIntegrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserIntegrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userIntegrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"lastTriggeredOn"}}]}}]}}]} as unknown as DocumentNode<UserIntegrationsQuery, UserIntegrationsQueryVariables>;
 export const UserNotificationPlatformsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserNotificationPlatforms"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userNotificationPlatforms"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}}]}}]} as unknown as DocumentNode<UserNotificationPlatformsQuery, UserNotificationPlatformsQueryVariables>;
 export const UsersListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UsersList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"query"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"usersList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"Variable","name":{"kind":"Name","value":"query"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}}]}}]}}]} as unknown as DocumentNode<UsersListQuery, UsersListQueryVariables>;
-export const UserUpcomingCalendarEventsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserUpcomingCalendarEvents"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UserUpcomingCalendarEventInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userUpcomingCalendarEvents"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CalendarEventPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CalendarEventPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"GraphqlCalendarEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"calendarEventId"}},{"kind":"Field","name":{"kind":"Name","value":"metadataId"}},{"kind":"Field","name":{"kind":"Name","value":"metadataTitle"}},{"kind":"Field","name":{"kind":"Name","value":"metadataLot"}},{"kind":"Field","name":{"kind":"Name","value":"metadataImage"}},{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}}]}}]} as unknown as DocumentNode<UserUpcomingCalendarEventsQuery, UserUpcomingCalendarEventsQueryVariables>;
-export const UserCalendarEventsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserCalendarEvents"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UserCalendarEventInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userCalendarEvents"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"events"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CalendarEventPart"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CalendarEventPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"GraphqlCalendarEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"calendarEventId"}},{"kind":"Field","name":{"kind":"Name","value":"metadataId"}},{"kind":"Field","name":{"kind":"Name","value":"metadataTitle"}},{"kind":"Field","name":{"kind":"Name","value":"metadataLot"}},{"kind":"Field","name":{"kind":"Name","value":"metadataImage"}},{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}}]}}]} as unknown as DocumentNode<UserCalendarEventsQuery, UserCalendarEventsQueryVariables>;
+export const UserUpcomingCalendarEventsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserUpcomingCalendarEvents"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UserUpcomingCalendarEventInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userUpcomingCalendarEvents"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CalendarEventPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CalendarEventPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"GraphqlCalendarEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"metadataId"}},{"kind":"Field","name":{"kind":"Name","value":"metadataLot"}},{"kind":"Field","name":{"kind":"Name","value":"episodeName"}},{"kind":"Field","name":{"kind":"Name","value":"metadataTitle"}},{"kind":"Field","name":{"kind":"Name","value":"metadataImage"}},{"kind":"Field","name":{"kind":"Name","value":"calendarEventId"}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}}]}}]} as unknown as DocumentNode<UserUpcomingCalendarEventsQuery, UserUpcomingCalendarEventsQueryVariables>;
+export const UserCalendarEventsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserCalendarEvents"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UserCalendarEventInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userCalendarEvents"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"events"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CalendarEventPart"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CalendarEventPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"GraphqlCalendarEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"metadataId"}},{"kind":"Field","name":{"kind":"Name","value":"metadataLot"}},{"kind":"Field","name":{"kind":"Name","value":"episodeName"}},{"kind":"Field","name":{"kind":"Name","value":"metadataTitle"}},{"kind":"Field","name":{"kind":"Name","value":"metadataImage"}},{"kind":"Field","name":{"kind":"Name","value":"calendarEventId"}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}}]}}]} as unknown as DocumentNode<UserCalendarEventsQuery, UserCalendarEventsQueryVariables>;
 export const MetadataPartialDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataPartialDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"metadataId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataPartialDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"metadataId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"metadataId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"publishYear"}}]}}]}}]} as unknown as DocumentNode<MetadataPartialDetailsQuery, MetadataPartialDetailsQueryVariables>;
 export const MetadataGroupsListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataGroupsList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SearchInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataGroupsList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"}}]}}]}}]} as unknown as DocumentNode<MetadataGroupsListQuery, MetadataGroupsListQueryVariables>;
 export const PeopleListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PeopleList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PeopleListInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"peopleList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"}}]}}]}}]} as unknown as DocumentNode<PeopleListQuery, PeopleListQueryVariables>;
\ No newline at end of file
diff --git a/libs/graphql/src/backend/queries/fragments.gql b/libs/graphql/src/backend/queries/fragments.gql
index 7d707dd042..2af0747075 100644
--- a/libs/graphql/src/backend/queries/fragments.gql
+++ b/libs/graphql/src/backend/queries/fragments.gql
@@ -17,12 +17,13 @@ fragment SeenMangaExtraInformationPart on SeenMangaExtraInformation {
 }
 
 fragment CalendarEventPart on GraphqlCalendarEvent {
-  calendarEventId
+  date
   metadataId
-  metadataTitle
   metadataLot
+  episodeName
+  metadataTitle
   metadataImage
-  date
+  calendarEventId
   showExtraInformation {
     ...SeenShowExtraInformationPart
   }


Test patch: