From 1caa9cac8e6ef041f3f5cbe8866d56e084a28ea2 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Wed, 23 Oct 2024 22:35:03 +0530 Subject: [PATCH 01/30] chore(frontend): do not allow showing/hiding details --- apps/frontend/app/lib/state/fitness.ts | 5 +- .../app/routes/_dashboard.fitness.$action.tsx | 266 +++++++----------- 2 files changed, 107 insertions(+), 164 deletions(-) diff --git a/apps/frontend/app/lib/state/fitness.ts b/apps/frontend/app/lib/state/fitness.ts index f5a7e1fa2d..28a94120a3 100644 --- a/apps/frontend/app/lib/state/fitness.ts +++ b/apps/frontend/app/lib/state/fitness.ts @@ -55,7 +55,6 @@ export type Exercise = { images: Array; isCollapsed?: boolean; sets: Array; - isShowDetailsOpen: boolean; openedDetailsTab?: "images" | "history"; alreadyDoneSets: Array; }; @@ -274,7 +273,7 @@ export const duplicateOldWorkout = async ( inProgress.updateWorkoutId = params.updateWorkoutId; inProgress.updateWorkoutTemplateId = params.updateWorkoutTemplateId; inProgress.comment = workoutInformation.comment || undefined; - for (const [exerciseIdx, ex] of workoutInformation.exercises.entries()) { + for (const ex of workoutInformation.exercises) { const sets = ex.sets.map((v) => convertHistorySetToCurrentSet( v, @@ -284,7 +283,6 @@ export const duplicateOldWorkout = async ( const exerciseDetails = await getExerciseDetails(ex.name); inProgress.exercises.push({ identifier: randomUUID(), - isShowDetailsOpen: exerciseIdx === 0, images: [], videos: [], alreadyDoneSets: sets.map((s) => ({ statistic: s.statistic })), @@ -370,7 +368,6 @@ export const addExerciseToWorkout = async ( } draft.exercises.push({ identifier: randomUUID(), - isShowDetailsOpen: true, exerciseId: ex.name, lot: ex.lot, sets, diff --git a/apps/frontend/app/routes/_dashboard.fitness.$action.tsx b/apps/frontend/app/routes/_dashboard.fitness.$action.tsx index 4cf92f4248..ee71800cae 100644 --- a/apps/frontend/app/routes/_dashboard.fitness.$action.tsx +++ b/apps/frontend/app/routes/_dashboard.fitness.$action.tsx @@ -52,10 +52,8 @@ import { import { CreateOrUpdateUserWorkoutDocument, CreateOrUpdateUserWorkoutTemplateDocument, - type ExerciseDetailsQuery, ExerciseLot, SetLot, - type UserExerciseDetailsQuery, UserUnitSystem, type WorkoutSetStatistic, } from "@ryot/generated/graphql/backend/graphql"; @@ -80,7 +78,6 @@ import { IconDroplet, IconDropletFilled, IconDropletHalf2Filled, - IconInfoCircle, IconLayersIntersect, IconPhoto, IconReorder, @@ -919,13 +916,6 @@ const focusOnExercise = (idx: number) => { }, 800); }; -const exerciseHasDetailsToShow = ( - details?: ExerciseDetailsQuery["exerciseDetails"], - userDetails?: UserExerciseDetailsQuery["userExerciseDetails"], -) => - (details?.attributes.images.length || 0) > 0 || - (userDetails?.history?.length || 0) > 0; - const ExerciseDisplay = (props: { exerciseIdx: number; stopTimer: () => void; @@ -1181,24 +1171,6 @@ const ExerciseDisplay = (props: { > Replace exercise - {exerciseHasDetailsToShow( - exerciseDetails, - userExerciseDetails, - ) ? ( - } - onClick={() => { - setCurrentWorkout( - produce(currentWorkout, (draft) => { - draft.exercises[props.exerciseIdx].isShowDetailsOpen = - !exercise.isShowDetailsOpen; - }), - ); - }} - > - {exercise.isShowDetailsOpen ? "Hide" : "Show"} details - - ) : null} } onClick={props.reorderDrawerToggle} @@ -1250,123 +1222,113 @@ const ExerciseDisplay = (props: { note={note} /> ))} - {exercise.isShowDetailsOpen ? ( - - {match(exercise.openedDetailsTab) - .with("images", undefined, () => ( - - - {exerciseDetails?.attributes.images.map((i) => ( - - ))} - - - )) - .with("history", () => ( - - {exerciseHistory?.map((history, idx) => ( - - {getSurroundingElements( - exerciseHistory, - activeHistoryIdx, - ).includes(idx) ? ( - { - if (!coreDetails.isPro) { - notifications.show({ - color: "red", - message: - "Ryot Pro required to copy sets from other workouts", - }); - return; - } - const workout = await getWorkoutDetails( - history.workoutId, - ); - const yes = await confirmWrapper({ - confirmation: `Are you sure you want to copy all sets from "${workout.details.name}"?`, - }); - if (yes) { - const sets = - workout.details.information.exercises[ - history.idx - ].sets; - const converted = sets.map((set) => - convertHistorySetToCurrentSet(set), - ); - setCurrentWorkout( - produce(currentWorkout, (draft) => { - draft.exercises[ - props.exerciseIdx - ].sets.push(...converted); - }), - ); - } - }} - /> - ) : null} - + + {match(exercise.openedDetailsTab) + .with("images", undefined, () => ( + + + {exerciseDetails?.attributes.images.map((i) => ( + ))} - - )) - .exhaustive()} - {(userExerciseDetails?.history?.length || 0) > 0 ? ( - { - if (!coreDetails.isPro) { - notifications.show({ - color: "red", - message: PRO_REQUIRED_MESSAGE, - }); - return; - } - setCurrentWorkout( - produce(currentWorkout, (draft) => { - draft.exercises[ - props.exerciseIdx - ].openedDetailsTab = - exercise.openedDetailsTab === "images" - ? "history" - : "images"; - }), - ); - }} + + + )) + .with("history", () => ( + - {match(exercise.openedDetailsTab) - .with("images", undefined, () => ) - .with("history", () => ) - .exhaustive()} - - ) : null} - - ) : null} + {exerciseHistory?.map((history, idx) => ( + + {getSurroundingElements( + exerciseHistory, + activeHistoryIdx, + ).includes(idx) ? ( + { + if (!coreDetails.isPro) { + notifications.show({ + color: "red", + message: + "Ryot Pro required to copy sets from other workouts", + }); + return; + } + const workout = await getWorkoutDetails( + history.workoutId, + ); + const yes = await confirmWrapper({ + confirmation: `Are you sure you want to copy all sets from "${workout.details.name}"?`, + }); + if (yes) { + const sets = + workout.details.information.exercises[ + history.idx + ].sets; + const converted = sets.map((set) => + convertHistorySetToCurrentSet(set), + ); + setCurrentWorkout( + produce(currentWorkout, (draft) => { + draft.exercises[ + props.exerciseIdx + ].sets.push(...converted); + }), + ); + } + }} + /> + ) : null} + + ))} + + )) + .exhaustive()} + {(userExerciseDetails?.history?.length || 0) > 0 ? ( + { + if (!coreDetails.isPro) { + notifications.show({ + color: "red", + message: PRO_REQUIRED_MESSAGE, + }); + return; + } + setCurrentWorkout( + produce(currentWorkout, (draft) => { + draft.exercises[props.exerciseIdx].openedDetailsTab = + exercise.openedDetailsTab === "images" + ? "history" + : "images"; + }), + ); + }} + > + {match(exercise.openedDetailsTab) + .with("images", undefined, () => ) + .with("history", () => ) + .exhaustive()} + + ) : null} + SET @@ -1531,12 +1493,6 @@ const SetDisplay = (props: { const set = useGetSetAtIndex(props.exerciseIdx, props.setIdx); const [isEditingRestTimer, setIsEditingRestTimer] = useState(false); const [value, setValue] = useDebouncedState(set?.note || "", 500); - const { data: exerciseDetails } = useQuery( - getExerciseDetailsQuery(exercise.exerciseId), - ); - const { data: userExerciseDetails } = useQuery( - getUserExerciseDetailsQuery(exercise.exerciseId), - ); const playCheckSound = () => { const sound = new Howl({ src: ["/check.mp3"] }); @@ -1795,19 +1751,9 @@ const SetDisplay = (props: { focusOnExercise(nextSet.exerciseIdx); if (nextSet.wasLastSet) { currentExercise.isCollapsed = true; - currentExercise.isShowDetailsOpen = false; const nextExercise = draft.exercises[nextSet.exerciseIdx]; - const nextExerciseHasDetailsToShow = - nextExercise && - exerciseHasDetailsToShow( - exerciseDetails, - userExerciseDetails, - ); - if (nextExerciseHasDetailsToShow) { - nextExercise.isCollapsed = false; - nextExercise.isShowDetailsOpen = true; - } + if (nextExercise) nextExercise.isCollapsed = false; } } }), From 1ecb6ff14ae4364618b7164ff559848d50380ae1 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Wed, 23 Oct 2024 22:37:38 +0530 Subject: [PATCH 02/30] refactor(backend): change order of attributes --- crates/services/integration/src/lib.rs | 2 +- crates/services/integration/src/push/radarr.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/services/integration/src/lib.rs b/crates/services/integration/src/lib.rs index 8f7abf5121..d6793fde98 100644 --- a/crates/services/integration/src/lib.rs +++ b/crates/services/integration/src/lib.rs @@ -195,8 +195,8 @@ impl IntegrationService { specifics.radarr_base_url.unwrap(), specifics.radarr_api_key.unwrap(), specifics.radarr_profile_id.unwrap(), - specifics.radarr_root_folder_path.unwrap(), entity_id, + specifics.radarr_root_folder_path.unwrap(), ); radarr.push_progress().await } diff --git a/crates/services/integration/src/push/radarr.rs b/crates/services/integration/src/push/radarr.rs index f625db5c5d..d5d512af80 100644 --- a/crates/services/integration/src/push/radarr.rs +++ b/crates/services/integration/src/push/radarr.rs @@ -12,8 +12,8 @@ pub(crate) struct RadarrPushIntegration { base_url: String, api_key: String, profile_id: i32, - root_folder_path: String, tmdb_id: String, + root_folder_path: String, } impl RadarrPushIntegration { @@ -21,15 +21,15 @@ impl RadarrPushIntegration { base_url: String, api_key: String, profile_id: i32, - root_folder_path: String, tmdb_id: String, + root_folder_path: String, ) -> Self { Self { base_url, api_key, profile_id, - root_folder_path, tmdb_id, + root_folder_path, } } From b6fba642da7a87a9abfdd75f46f44982e8cce74e Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Wed, 23 Oct 2024 22:41:36 +0530 Subject: [PATCH 03/30] feat(backend): send movie/show titles --- crates/services/integration/src/lib.rs | 4 +++- crates/services/integration/src/push/radarr.rs | 8 ++++++-- crates/services/integration/src/push/sonarr.rs | 12 ++++++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/crates/services/integration/src/lib.rs b/crates/services/integration/src/lib.rs index d6793fde98..a71dd78bd8 100644 --- a/crates/services/integration/src/lib.rs +++ b/crates/services/integration/src/lib.rs @@ -196,6 +196,7 @@ impl IntegrationService { specifics.radarr_api_key.unwrap(), specifics.radarr_profile_id.unwrap(), entity_id, + metadata.title, specifics.radarr_root_folder_path.unwrap(), ); radarr.push_progress().await @@ -205,8 +206,9 @@ impl IntegrationService { specifics.sonarr_base_url.unwrap(), specifics.sonarr_api_key.unwrap(), specifics.sonarr_profile_id.unwrap(), - specifics.sonarr_root_folder_path.unwrap(), entity_id, + metadata.title, + specifics.sonarr_root_folder_path.unwrap(), ); sonarr.push_progress().await } diff --git a/crates/services/integration/src/push/radarr.rs b/crates/services/integration/src/push/radarr.rs index d5d512af80..2e7e696089 100644 --- a/crates/services/integration/src/push/radarr.rs +++ b/crates/services/integration/src/push/radarr.rs @@ -13,6 +13,7 @@ pub(crate) struct RadarrPushIntegration { api_key: String, profile_id: i32, tmdb_id: String, + movie_title: String, root_folder_path: String, } @@ -22,13 +23,15 @@ impl RadarrPushIntegration { api_key: String, profile_id: i32, tmdb_id: String, + movie_title: String, root_folder_path: String, ) -> Self { Self { - base_url, api_key, - profile_id, tmdb_id, + base_url, + profile_id, + movie_title, root_folder_path, } } @@ -45,6 +48,7 @@ impl RadarrPushIntegration { resource.quality_profile_id = Some(self.profile_id); resource.root_folder_path = Some(Some(self.root_folder_path.clone())); resource.monitored = Some(true); + resource.title = Some(Some(self.movie_title.clone())); let mut options = RadarrAddMovieOptions::new(); options.search_for_movie = Some(true); resource.add_options = Some(Box::new(options)); diff --git a/crates/services/integration/src/push/sonarr.rs b/crates/services/integration/src/push/sonarr.rs index 1024a8dac1..2d10f379e1 100644 --- a/crates/services/integration/src/push/sonarr.rs +++ b/crates/services/integration/src/push/sonarr.rs @@ -12,8 +12,9 @@ pub(crate) struct SonarrPushIntegration { base_url: String, api_key: String, profile_id: i32, - root_folder_path: String, tvdb_id: String, + show_title: String, + root_folder_path: String, } impl SonarrPushIntegration { @@ -21,15 +22,17 @@ impl SonarrPushIntegration { base_url: String, api_key: String, profile_id: i32, - root_folder_path: String, tvdb_id: String, + show_title: String, + root_folder_path: String, ) -> Self { Self { - base_url, + tvdb_id, api_key, + base_url, + show_title, profile_id, root_folder_path, - tvdb_id, } } @@ -47,6 +50,7 @@ impl SonarrPushIntegration { resource.root_folder_path = Some(Some(self.root_folder_path.clone())); resource.monitored = Some(true); resource.season_folder = Some(true); + resource.title = Some(Some(self.show_title.clone())); let mut options = SonarrAddSeriesOptions::new(); options.search_for_missing_episodes = Some(true); resource.add_options = Some(Box::new(options)); From 550d9c14832e0b7160f5c2782ba3b5b02ccef7cc Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Wed, 23 Oct 2024 22:42:11 +0530 Subject: [PATCH 04/30] ci: Run CI From 1be73ae02efa330887adc4cfccdc9ceb2da4bad5 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Wed, 23 Oct 2024 22:47:09 +0530 Subject: [PATCH 05/30] chore(frontend): remove upper margin --- apps/frontend/app/routes/_dashboard.fitness.$action.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/app/routes/_dashboard.fitness.$action.tsx b/apps/frontend/app/routes/_dashboard.fitness.$action.tsx index ee71800cae..ddf18348ab 100644 --- a/apps/frontend/app/routes/_dashboard.fitness.$action.tsx +++ b/apps/frontend/app/routes/_dashboard.fitness.$action.tsx @@ -1774,7 +1774,7 @@ const SetDisplay = (props: { onChange={(v) => setValue(v.currentTarget.value)} /> ) : undefined} - + {set.restTimer && !didCurrentSetActivateTimer ? ( Date: Fri, 25 Oct 2024 06:11:00 +0530 Subject: [PATCH 06/30] chore(frontend): revert all exercise changes --- apps/frontend/app/lib/state/fitness.ts | 5 +- .../app/routes/_dashboard.fitness.$action.tsx | 268 +++++++++++------- 2 files changed, 165 insertions(+), 108 deletions(-) diff --git a/apps/frontend/app/lib/state/fitness.ts b/apps/frontend/app/lib/state/fitness.ts index 28a94120a3..f5a7e1fa2d 100644 --- a/apps/frontend/app/lib/state/fitness.ts +++ b/apps/frontend/app/lib/state/fitness.ts @@ -55,6 +55,7 @@ export type Exercise = { images: Array; isCollapsed?: boolean; sets: Array; + isShowDetailsOpen: boolean; openedDetailsTab?: "images" | "history"; alreadyDoneSets: Array; }; @@ -273,7 +274,7 @@ export const duplicateOldWorkout = async ( inProgress.updateWorkoutId = params.updateWorkoutId; inProgress.updateWorkoutTemplateId = params.updateWorkoutTemplateId; inProgress.comment = workoutInformation.comment || undefined; - for (const ex of workoutInformation.exercises) { + for (const [exerciseIdx, ex] of workoutInformation.exercises.entries()) { const sets = ex.sets.map((v) => convertHistorySetToCurrentSet( v, @@ -283,6 +284,7 @@ export const duplicateOldWorkout = async ( const exerciseDetails = await getExerciseDetails(ex.name); inProgress.exercises.push({ identifier: randomUUID(), + isShowDetailsOpen: exerciseIdx === 0, images: [], videos: [], alreadyDoneSets: sets.map((s) => ({ statistic: s.statistic })), @@ -368,6 +370,7 @@ export const addExerciseToWorkout = async ( } draft.exercises.push({ identifier: randomUUID(), + isShowDetailsOpen: true, exerciseId: ex.name, lot: ex.lot, sets, diff --git a/apps/frontend/app/routes/_dashboard.fitness.$action.tsx b/apps/frontend/app/routes/_dashboard.fitness.$action.tsx index ddf18348ab..4cf92f4248 100644 --- a/apps/frontend/app/routes/_dashboard.fitness.$action.tsx +++ b/apps/frontend/app/routes/_dashboard.fitness.$action.tsx @@ -52,8 +52,10 @@ import { import { CreateOrUpdateUserWorkoutDocument, CreateOrUpdateUserWorkoutTemplateDocument, + type ExerciseDetailsQuery, ExerciseLot, SetLot, + type UserExerciseDetailsQuery, UserUnitSystem, type WorkoutSetStatistic, } from "@ryot/generated/graphql/backend/graphql"; @@ -78,6 +80,7 @@ import { IconDroplet, IconDropletFilled, IconDropletHalf2Filled, + IconInfoCircle, IconLayersIntersect, IconPhoto, IconReorder, @@ -916,6 +919,13 @@ const focusOnExercise = (idx: number) => { }, 800); }; +const exerciseHasDetailsToShow = ( + details?: ExerciseDetailsQuery["exerciseDetails"], + userDetails?: UserExerciseDetailsQuery["userExerciseDetails"], +) => + (details?.attributes.images.length || 0) > 0 || + (userDetails?.history?.length || 0) > 0; + const ExerciseDisplay = (props: { exerciseIdx: number; stopTimer: () => void; @@ -1171,6 +1181,24 @@ const ExerciseDisplay = (props: { > Replace exercise + {exerciseHasDetailsToShow( + exerciseDetails, + userExerciseDetails, + ) ? ( + } + onClick={() => { + setCurrentWorkout( + produce(currentWorkout, (draft) => { + draft.exercises[props.exerciseIdx].isShowDetailsOpen = + !exercise.isShowDetailsOpen; + }), + ); + }} + > + {exercise.isShowDetailsOpen ? "Hide" : "Show"} details + + ) : null} } onClick={props.reorderDrawerToggle} @@ -1222,113 +1250,123 @@ const ExerciseDisplay = (props: { note={note} /> ))} - - {match(exercise.openedDetailsTab) - .with("images", undefined, () => ( - - - {exerciseDetails?.attributes.images.map((i) => ( - + {exercise.isShowDetailsOpen ? ( + + {match(exercise.openedDetailsTab) + .with("images", undefined, () => ( + + + {exerciseDetails?.attributes.images.map((i) => ( + + ))} + + + )) + .with("history", () => ( + + {exerciseHistory?.map((history, idx) => ( + + {getSurroundingElements( + exerciseHistory, + activeHistoryIdx, + ).includes(idx) ? ( + { + if (!coreDetails.isPro) { + notifications.show({ + color: "red", + message: + "Ryot Pro required to copy sets from other workouts", + }); + return; + } + const workout = await getWorkoutDetails( + history.workoutId, + ); + const yes = await confirmWrapper({ + confirmation: `Are you sure you want to copy all sets from "${workout.details.name}"?`, + }); + if (yes) { + const sets = + workout.details.information.exercises[ + history.idx + ].sets; + const converted = sets.map((set) => + convertHistorySetToCurrentSet(set), + ); + setCurrentWorkout( + produce(currentWorkout, (draft) => { + draft.exercises[ + props.exerciseIdx + ].sets.push(...converted); + }), + ); + } + }} + /> + ) : null} + ))} - - - )) - .with("history", () => ( - + )) + .exhaustive()} + {(userExerciseDetails?.history?.length || 0) > 0 ? ( + { + if (!coreDetails.isPro) { + notifications.show({ + color: "red", + message: PRO_REQUIRED_MESSAGE, + }); + return; + } + setCurrentWorkout( + produce(currentWorkout, (draft) => { + draft.exercises[ + props.exerciseIdx + ].openedDetailsTab = + exercise.openedDetailsTab === "images" + ? "history" + : "images"; + }), + ); + }} > - {exerciseHistory?.map((history, idx) => ( - - {getSurroundingElements( - exerciseHistory, - activeHistoryIdx, - ).includes(idx) ? ( - { - if (!coreDetails.isPro) { - notifications.show({ - color: "red", - message: - "Ryot Pro required to copy sets from other workouts", - }); - return; - } - const workout = await getWorkoutDetails( - history.workoutId, - ); - const yes = await confirmWrapper({ - confirmation: `Are you sure you want to copy all sets from "${workout.details.name}"?`, - }); - if (yes) { - const sets = - workout.details.information.exercises[ - history.idx - ].sets; - const converted = sets.map((set) => - convertHistorySetToCurrentSet(set), - ); - setCurrentWorkout( - produce(currentWorkout, (draft) => { - draft.exercises[ - props.exerciseIdx - ].sets.push(...converted); - }), - ); - } - }} - /> - ) : null} - - ))} - - )) - .exhaustive()} - {(userExerciseDetails?.history?.length || 0) > 0 ? ( - { - if (!coreDetails.isPro) { - notifications.show({ - color: "red", - message: PRO_REQUIRED_MESSAGE, - }); - return; - } - setCurrentWorkout( - produce(currentWorkout, (draft) => { - draft.exercises[props.exerciseIdx].openedDetailsTab = - exercise.openedDetailsTab === "images" - ? "history" - : "images"; - }), - ); - }} - > - {match(exercise.openedDetailsTab) - .with("images", undefined, () => ) - .with("history", () => ) - .exhaustive()} - - ) : null} - + {match(exercise.openedDetailsTab) + .with("images", undefined, () => ) + .with("history", () => ) + .exhaustive()} + + ) : null} + + ) : null} SET @@ -1493,6 +1531,12 @@ const SetDisplay = (props: { const set = useGetSetAtIndex(props.exerciseIdx, props.setIdx); const [isEditingRestTimer, setIsEditingRestTimer] = useState(false); const [value, setValue] = useDebouncedState(set?.note || "", 500); + const { data: exerciseDetails } = useQuery( + getExerciseDetailsQuery(exercise.exerciseId), + ); + const { data: userExerciseDetails } = useQuery( + getUserExerciseDetailsQuery(exercise.exerciseId), + ); const playCheckSound = () => { const sound = new Howl({ src: ["/check.mp3"] }); @@ -1751,9 +1795,19 @@ const SetDisplay = (props: { focusOnExercise(nextSet.exerciseIdx); if (nextSet.wasLastSet) { currentExercise.isCollapsed = true; + currentExercise.isShowDetailsOpen = false; const nextExercise = draft.exercises[nextSet.exerciseIdx]; - if (nextExercise) nextExercise.isCollapsed = false; + const nextExerciseHasDetailsToShow = + nextExercise && + exerciseHasDetailsToShow( + exerciseDetails, + userExerciseDetails, + ); + if (nextExerciseHasDetailsToShow) { + nextExercise.isCollapsed = false; + nextExercise.isShowDetailsOpen = true; + } } } }), @@ -1774,7 +1828,7 @@ const SetDisplay = (props: { onChange={(v) => setValue(v.currentTarget.value)} /> ) : undefined} - + {set.restTimer && !didCurrentSetActivateTimer ? ( Date: Fri, 25 Oct 2024 06:11:12 +0530 Subject: [PATCH 07/30] ci: Run CI From ae7b17a8c6c0b04945eb0a93cd2eecd8bcdd1971 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sat, 26 Oct 2024 09:23:49 +0530 Subject: [PATCH 08/30] refactor(services/integration): change order of args --- crates/services/integration/src/lib.rs | 4 ++-- crates/services/integration/src/push/radarr.rs | 4 ++-- crates/services/integration/src/push/sonarr.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/services/integration/src/lib.rs b/crates/services/integration/src/lib.rs index a71dd78bd8..8d6fb283ee 100644 --- a/crates/services/integration/src/lib.rs +++ b/crates/services/integration/src/lib.rs @@ -192,10 +192,10 @@ impl IntegrationService { let _push_result = match integration.provider { IntegrationProvider::Radarr => { let radarr = RadarrPushIntegration::new( - specifics.radarr_base_url.unwrap(), specifics.radarr_api_key.unwrap(), specifics.radarr_profile_id.unwrap(), entity_id, + specifics.radarr_base_url.unwrap(), metadata.title, specifics.radarr_root_folder_path.unwrap(), ); @@ -203,10 +203,10 @@ impl IntegrationService { } IntegrationProvider::Sonarr => { let sonarr = SonarrPushIntegration::new( - specifics.sonarr_base_url.unwrap(), specifics.sonarr_api_key.unwrap(), specifics.sonarr_profile_id.unwrap(), entity_id, + specifics.sonarr_base_url.unwrap(), metadata.title, specifics.sonarr_root_folder_path.unwrap(), ); diff --git a/crates/services/integration/src/push/radarr.rs b/crates/services/integration/src/push/radarr.rs index 2e7e696089..9760fc05ef 100644 --- a/crates/services/integration/src/push/radarr.rs +++ b/crates/services/integration/src/push/radarr.rs @@ -9,20 +9,20 @@ use radarr_api_rs::{ use traits::TraceOk; pub(crate) struct RadarrPushIntegration { - base_url: String, api_key: String, profile_id: i32, tmdb_id: String, + base_url: String, movie_title: String, root_folder_path: String, } impl RadarrPushIntegration { pub const fn new( - base_url: String, api_key: String, profile_id: i32, tmdb_id: String, + base_url: String, movie_title: String, root_folder_path: String, ) -> Self { diff --git a/crates/services/integration/src/push/sonarr.rs b/crates/services/integration/src/push/sonarr.rs index 2d10f379e1..019d6759c9 100644 --- a/crates/services/integration/src/push/sonarr.rs +++ b/crates/services/integration/src/push/sonarr.rs @@ -9,20 +9,20 @@ use sonarr_api_rs::{ use traits::TraceOk; pub(crate) struct SonarrPushIntegration { - base_url: String, api_key: String, profile_id: i32, tvdb_id: String, + base_url: String, show_title: String, root_folder_path: String, } impl SonarrPushIntegration { pub const fn new( - base_url: String, api_key: String, profile_id: i32, tvdb_id: String, + base_url: String, show_title: String, root_folder_path: String, ) -> Self { From 928c8b254bf638d17c0e1e849975cd0d530f9134 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sat, 26 Oct 2024 09:28:57 +0530 Subject: [PATCH 09/30] fix(services/integration): send only requests for shows and movies --- crates/services/integration/src/lib.rs | 7 ++++--- crates/services/integration/src/push/jellyfin.rs | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/crates/services/integration/src/lib.rs b/crates/services/integration/src/lib.rs index 8d6fb283ee..84ea2a721e 100644 --- a/crates/services/integration/src/lib.rs +++ b/crates/services/integration/src/lib.rs @@ -221,12 +221,12 @@ impl IntegrationService { } pub async fn handle_on_seen_complete(&self, id: String) -> GqlResult<()> { - let (seen, show_extra_information, metadata_title) = Seen::find_by_id(id) + let (seen, show_extra_information, metadata_title, metadata_lot) = Seen::find_by_id(id) .left_join(Metadata) .select_only() .columns([seen::Column::UserId, seen::Column::ShowExtraInformation]) - .columns([metadata::Column::Title]) - .into_tuple::<(String, Option, String)>() + .columns([metadata::Column::Title, metadata::Column::Lot]) + .into_tuple::<(String, Option, String, MediaLot)>() .one(&self.0.db) .await? .ok_or_else(|| Error::new("Seen with the given ID could not be found"))?; @@ -244,6 +244,7 @@ impl IntegrationService { specifics.jellyfin_push_base_url.unwrap(), specifics.jellyfin_push_username.unwrap(), specifics.jellyfin_push_password.unwrap(), + &metadata_lot, &metadata_title, &show_extra_information, ); diff --git a/crates/services/integration/src/push/jellyfin.rs b/crates/services/integration/src/push/jellyfin.rs index 6713adbc46..6eb0a31c5a 100644 --- a/crates/services/integration/src/push/jellyfin.rs +++ b/crates/services/integration/src/push/jellyfin.rs @@ -1,3 +1,5 @@ +use common_utils::ryot_log; +use enums::MediaLot; use external_utils::jellyfin::{get_authenticated_client, ItemsResponse}; use media_models::SeenShowExtraInformation; use serde_json::json; @@ -7,6 +9,7 @@ pub(crate) struct JellyfinPushIntegration<'a> { base_url: String, username: String, password: String, + metadata_lot: &'a MediaLot, metadata_title: &'a String, show_extra_information: &'a Option, } @@ -16,6 +19,7 @@ impl<'a> JellyfinPushIntegration<'a> { base_url: String, username: String, password: String, + metadata_lot: &'a MediaLot, metadata_title: &'a String, show_extra_information: &'a Option, ) -> Self { @@ -23,12 +27,24 @@ impl<'a> JellyfinPushIntegration<'a> { base_url, username, password, + metadata_lot, metadata_title, show_extra_information, } } pub async fn push_progress(&self) -> anyhow::Result<()> { + match *self.metadata_lot { + MediaLot::Movie | MediaLot::Show => {} + _ => { + ryot_log!( + debug, + "Not pushing {:#?} progress for jellyfin push integration", + self.metadata_lot + ); + return Ok(()); + } + } let (client, user_id) = get_authenticated_client(&self.base_url, &self.username, &self.password).await?; let json = From fa6cbbfc2c8586a4b5e69605733dade592a4739e Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sat, 26 Oct 2024 09:30:12 +0530 Subject: [PATCH 10/30] refactor(services/integration): change name of attribute and func args --- crates/services/integration/src/push/radarr.rs | 8 ++++---- crates/services/integration/src/push/sonarr.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/services/integration/src/push/radarr.rs b/crates/services/integration/src/push/radarr.rs index 9760fc05ef..7e80909d2a 100644 --- a/crates/services/integration/src/push/radarr.rs +++ b/crates/services/integration/src/push/radarr.rs @@ -13,7 +13,7 @@ pub(crate) struct RadarrPushIntegration { profile_id: i32, tmdb_id: String, base_url: String, - movie_title: String, + metadata_title: String, root_folder_path: String, } @@ -23,7 +23,7 @@ impl RadarrPushIntegration { profile_id: i32, tmdb_id: String, base_url: String, - movie_title: String, + metadata_title: String, root_folder_path: String, ) -> Self { Self { @@ -31,7 +31,7 @@ impl RadarrPushIntegration { tmdb_id, base_url, profile_id, - movie_title, + metadata_title, root_folder_path, } } @@ -48,7 +48,7 @@ impl RadarrPushIntegration { resource.quality_profile_id = Some(self.profile_id); resource.root_folder_path = Some(Some(self.root_folder_path.clone())); resource.monitored = Some(true); - resource.title = Some(Some(self.movie_title.clone())); + resource.title = Some(Some(self.metadata_title.clone())); let mut options = RadarrAddMovieOptions::new(); options.search_for_movie = Some(true); resource.add_options = Some(Box::new(options)); diff --git a/crates/services/integration/src/push/sonarr.rs b/crates/services/integration/src/push/sonarr.rs index 019d6759c9..1607022ddd 100644 --- a/crates/services/integration/src/push/sonarr.rs +++ b/crates/services/integration/src/push/sonarr.rs @@ -13,7 +13,7 @@ pub(crate) struct SonarrPushIntegration { profile_id: i32, tvdb_id: String, base_url: String, - show_title: String, + metadata_title: String, root_folder_path: String, } @@ -23,15 +23,15 @@ impl SonarrPushIntegration { profile_id: i32, tvdb_id: String, base_url: String, - show_title: String, + metadata_title: String, root_folder_path: String, ) -> Self { Self { tvdb_id, api_key, base_url, - show_title, profile_id, + metadata_title, root_folder_path, } } @@ -50,7 +50,7 @@ impl SonarrPushIntegration { resource.root_folder_path = Some(Some(self.root_folder_path.clone())); resource.monitored = Some(true); resource.season_folder = Some(true); - resource.title = Some(Some(self.show_title.clone())); + resource.title = Some(Some(self.metadata_title.clone())); let mut options = SonarrAddSeriesOptions::new(); options.search_for_missing_episodes = Some(true); resource.add_options = Some(Box::new(options)); From c8ec8f8747a0c94de561319bd67f7a575d3768e9 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sat, 26 Oct 2024 09:32:41 +0530 Subject: [PATCH 11/30] feat(services/integration): send only requests for shows and movies for radarr and sonarr --- crates/services/integration/src/lib.rs | 2 ++ crates/services/integration/src/push/radarr.rs | 8 ++++++++ crates/services/integration/src/push/sonarr.rs | 8 ++++++++ 3 files changed, 18 insertions(+) diff --git a/crates/services/integration/src/lib.rs b/crates/services/integration/src/lib.rs index 84ea2a721e..965539399f 100644 --- a/crates/services/integration/src/lib.rs +++ b/crates/services/integration/src/lib.rs @@ -196,6 +196,7 @@ impl IntegrationService { specifics.radarr_profile_id.unwrap(), entity_id, specifics.radarr_base_url.unwrap(), + metadata.lot, metadata.title, specifics.radarr_root_folder_path.unwrap(), ); @@ -207,6 +208,7 @@ impl IntegrationService { specifics.sonarr_profile_id.unwrap(), entity_id, specifics.sonarr_base_url.unwrap(), + metadata.lot, metadata.title, specifics.sonarr_root_folder_path.unwrap(), ); diff --git a/crates/services/integration/src/push/radarr.rs b/crates/services/integration/src/push/radarr.rs index 7e80909d2a..58b5907050 100644 --- a/crates/services/integration/src/push/radarr.rs +++ b/crates/services/integration/src/push/radarr.rs @@ -1,4 +1,5 @@ use common_utils::ryot_log; +use enums::MediaLot; use radarr_api_rs::{ apis::{ configuration::{ApiKey as RadarrApiKey, Configuration as RadarrConfiguration}, @@ -13,6 +14,7 @@ pub(crate) struct RadarrPushIntegration { profile_id: i32, tmdb_id: String, base_url: String, + metadata_lot: MediaLot, metadata_title: String, root_folder_path: String, } @@ -23,6 +25,7 @@ impl RadarrPushIntegration { profile_id: i32, tmdb_id: String, base_url: String, + metadata_lot: MediaLot, metadata_title: String, root_folder_path: String, ) -> Self { @@ -31,12 +34,17 @@ impl RadarrPushIntegration { tmdb_id, base_url, profile_id, + metadata_lot, metadata_title, root_folder_path, } } pub async fn push_progress(&self) -> anyhow::Result<()> { + if self.metadata_lot != MediaLot::Movie { + ryot_log!(debug, "Not a movie, skipping {:#?}", self.metadata_title); + return Ok(()); + } let mut configuration = RadarrConfiguration::new(); configuration.base_path = self.base_url.clone(); configuration.api_key = Some(RadarrApiKey { diff --git a/crates/services/integration/src/push/sonarr.rs b/crates/services/integration/src/push/sonarr.rs index 1607022ddd..72db381d8f 100644 --- a/crates/services/integration/src/push/sonarr.rs +++ b/crates/services/integration/src/push/sonarr.rs @@ -1,4 +1,5 @@ use common_utils::ryot_log; +use enums::MediaLot; use sonarr_api_rs::{ apis::{ configuration::{ApiKey as SonarrApiKey, Configuration as SonarrConfiguration}, @@ -13,6 +14,7 @@ pub(crate) struct SonarrPushIntegration { profile_id: i32, tvdb_id: String, base_url: String, + metadata_lot: MediaLot, metadata_title: String, root_folder_path: String, } @@ -23,6 +25,7 @@ impl SonarrPushIntegration { profile_id: i32, tvdb_id: String, base_url: String, + metadata_lot: MediaLot, metadata_title: String, root_folder_path: String, ) -> Self { @@ -31,12 +34,17 @@ impl SonarrPushIntegration { api_key, base_url, profile_id, + metadata_lot, metadata_title, root_folder_path, } } pub async fn push_progress(&self) -> anyhow::Result<()> { + if self.metadata_lot != MediaLot::Show { + ryot_log!(debug, "Not a show, skipping {:#?}", self.metadata_title); + return Ok(()); + } let mut configuration = SonarrConfiguration::new(); configuration.base_path = self.base_url.clone(); configuration.api_key = Some(SonarrApiKey { From 6e3c0e27d0ae7979759fa22db7aa6dfeab4b767e Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sat, 26 Oct 2024 09:32:48 +0530 Subject: [PATCH 12/30] ci: Run CI From 819cb3e888ccfbdbb510bac3d7c3ee4a9866944c Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sat, 26 Oct 2024 10:48:32 +0530 Subject: [PATCH 13/30] refactor(backend): use guard `let-else` statements --- crates/services/collection/src/lib.rs | 12 ++-- crates/services/fitness/src/lib.rs | 59 ++++++++-------- crates/services/integration/src/yank/komga.rs | 6 +- crates/services/miscellaneous/src/lib.rs | 68 +++++++++---------- crates/services/user/src/lib.rs | 46 ++++++------- crates/utils/database/src/lib.rs | 31 ++++----- 6 files changed, 104 insertions(+), 118 deletions(-) diff --git a/crates/services/collection/src/lib.rs b/crates/services/collection/src/lib.rs index ab77128d90..66e2912389 100644 --- a/crates/services/collection/src/lib.rs +++ b/crates/services/collection/src/lib.rs @@ -294,15 +294,11 @@ impl CollectionService { .filter(collection::Column::UserId.eq(user_id.to_owned())) .one(&self.0.db) .await?; - let resp = if let Some(c) = collection { - Collection::delete_by_id(c.id) - .exec(&self.0.db) - .await - .is_ok() - } else { - false + let Some(c) = collection else { + return Ok(false); }; - Ok(resp) + let resp = Collection::delete_by_id(c.id).exec(&self.0.db).await; + Ok(resp.is_ok()) } pub async fn add_entity_to_collection( diff --git a/crates/services/fitness/src/lib.rs b/crates/services/fitness/src/lib.rs index 624cb285f1..93b7fc2cc1 100644 --- a/crates/services/fitness/src/lib.rs +++ b/crates/services/fitness/src/lib.rs @@ -174,16 +174,15 @@ impl ExerciseService { workout_template_id: String, ) -> Result { pro_instance_guard(self.0.is_pro).await?; - if let Some(wkt) = WorkoutTemplate::find_by_id(workout_template_id) + let Some(wkt) = WorkoutTemplate::find_by_id(workout_template_id) .filter(workout_template::Column::UserId.eq(&user_id)) .one(&self.0.db) .await? - { - wkt.delete(&self.0.db).await?; - Ok(true) - } else { - Err(Error::new("Workout template does not exist for user")) - } + else { + return Err(Error::new("Workout template does not exist for user")); + }; + wkt.delete(&self.0.db).await?; + Ok(true) } pub async fn exercise_parameters(&self) -> Result { @@ -525,15 +524,14 @@ impl ExerciseService { user_id: String, timestamp: DateTimeUtc, ) -> Result { - if let Some(m) = UserMeasurement::find_by_id((timestamp, user_id)) + let Some(m) = UserMeasurement::find_by_id((timestamp, user_id)) .one(&self.0.db) .await? - { - m.delete(&self.0.db).await?; - Ok(true) - } else { - Ok(false) - } + else { + return Ok(false); + }; + m.delete(&self.0.db).await?; + Ok(true) } pub async fn create_or_update_user_workout( @@ -550,28 +548,27 @@ impl ExerciseService { user_id: String, input: UpdateUserWorkoutAttributesInput, ) -> Result { - if let Some(wkt) = Workout::find() + let Some(wkt) = Workout::find() .filter(workout::Column::UserId.eq(&user_id)) .filter(workout::Column::Id.eq(input.id)) .one(&self.0.db) .await? - { - let mut new_wkt: workout::ActiveModel = wkt.into(); - if let Some(d) = input.start_time { - new_wkt.start_time = ActiveValue::Set(d); - } - if let Some(d) = input.end_time { - new_wkt.end_time = ActiveValue::Set(d); - } - if new_wkt.is_changed() { - new_wkt.update(&self.0.db).await?; - deploy_job_to_re_evaluate_user_workouts(&user_id, &self.0).await; - Ok(true) - } else { - Ok(false) - } + else { + return Err(Error::new("Workout does not exist for user")); + }; + let mut new_wkt: workout::ActiveModel = wkt.into(); + if let Some(d) = input.start_time { + new_wkt.start_time = ActiveValue::Set(d); + } + if let Some(d) = input.end_time { + new_wkt.end_time = ActiveValue::Set(d); + } + if new_wkt.is_changed() { + new_wkt.update(&self.0.db).await?; + deploy_job_to_re_evaluate_user_workouts(&user_id, &self.0).await; + Ok(true) } else { - Err(Error::new("Workout does not exist for user")) + Ok(false) } } diff --git a/crates/services/integration/src/yank/komga.rs b/crates/services/integration/src/yank/komga.rs index 97bcf0d841..32cefd4562 100644 --- a/crates/services/integration/src/yank/komga.rs +++ b/crates/services/integration/src/yank/komga.rs @@ -93,14 +93,12 @@ mod komga_series { /// /// returns: The ID number if the extraction is successful fn extract_id(&self, url: String) -> Option { - if let Ok(parsed_url) = Url::parse(&url) { + Url::parse(&url).ok().and_then(|parsed_url| { parsed_url .path_segments() .and_then(|segments| segments.collect::>().get(1).cloned()) .map(String::from) - } else { - None - } + }) } /// Extracts the list of providers with a MediaSource,ID Tuple diff --git a/crates/services/miscellaneous/src/lib.rs b/crates/services/miscellaneous/src/lib.rs index 1589e4c802..b0f0b9cd75 100644 --- a/crates/services/miscellaneous/src/lib.rs +++ b/crates/services/miscellaneous/src/lib.rs @@ -1770,42 +1770,40 @@ ORDER BY RANDOM() LIMIT 10; seen_id: String, ) -> Result { let seen_item = Seen::find_by_id(seen_id).one(&self.0.db).await.unwrap(); - if let Some(si) = seen_item { - let cloned_seen = si.clone(); - let (ssn, sen) = match &si.show_extra_information { - Some(d) => (Some(d.season), Some(d.episode)), - None => (None, None), - }; - let pen = si.podcast_extra_information.as_ref().map(|d| d.episode); - let aen = si.anime_extra_information.as_ref().and_then(|d| d.episode); - let mcn = si.manga_extra_information.as_ref().and_then(|d| d.chapter); - let mvn = si.manga_extra_information.as_ref().and_then(|d| d.volume); - let cache = ApplicationCacheKey::ProgressUpdateCache { - show_season_number: ssn, - manga_volume_number: mvn, - show_episode_number: sen, - anime_episode_number: aen, - manga_chapter_number: mcn, - podcast_episode_number: pen, - user_id: user_id.to_owned(), - metadata_id: si.metadata_id.clone(), - }; - self.0.cache_service.delete(cache).await?; - let seen_id = si.id.clone(); - let metadata_id = si.metadata_id.clone(); - if &si.user_id != user_id { - return Err(Error::new( - "This seen item does not belong to this user".to_owned(), - )); - } - si.delete(&self.0.db).await.trace_ok(); - associate_user_with_entity(&self.0.db, user_id, metadata_id, EntityLot::Metadata) - .await?; - deploy_after_handle_media_seen_tasks(cloned_seen, &self.0).await?; - Ok(StringIdObject { id: seen_id }) - } else { - Err(Error::new("This seen item does not exist".to_owned())) + let Some(si) = seen_item else { + return Err(Error::new("This seen item does not exist".to_owned())); + }; + let cloned_seen = si.clone(); + let (ssn, sen) = match &si.show_extra_information { + Some(d) => (Some(d.season), Some(d.episode)), + None => (None, None), + }; + let pen = si.podcast_extra_information.as_ref().map(|d| d.episode); + let aen = si.anime_extra_information.as_ref().and_then(|d| d.episode); + let mcn = si.manga_extra_information.as_ref().and_then(|d| d.chapter); + let mvn = si.manga_extra_information.as_ref().and_then(|d| d.volume); + let cache = ApplicationCacheKey::ProgressUpdateCache { + show_season_number: ssn, + manga_volume_number: mvn, + show_episode_number: sen, + anime_episode_number: aen, + manga_chapter_number: mcn, + podcast_episode_number: pen, + user_id: user_id.to_owned(), + metadata_id: si.metadata_id.clone(), + }; + self.0.cache_service.delete(cache).await?; + let seen_id = si.id.clone(); + let metadata_id = si.metadata_id.clone(); + if &si.user_id != user_id { + return Err(Error::new( + "This seen item does not belong to this user".to_owned(), + )); } + si.delete(&self.0.db).await.trace_ok(); + associate_user_with_entity(&self.0.db, user_id, metadata_id, EntityLot::Metadata).await?; + deploy_after_handle_media_seen_tasks(cloned_seen, &self.0).await?; + Ok(StringIdObject { id: seen_id }) } async fn regenerate_user_summaries(&self) -> Result<()> { diff --git a/crates/services/user/src/lib.rs b/crates/services/user/src/lib.rs index 0496671f01..1e2ceea86d 100644 --- a/crates/services/user/src/lib.rs +++ b/crates/services/user/src/lib.rs @@ -217,24 +217,23 @@ impl UserService { ) -> Result { admin_account_guard(&self.0.db, &admin_user_id).await?; let maybe_user = User::find_by_id(to_delete_user_id).one(&self.0.db).await?; - if let Some(u) = maybe_user { - if self - .users_list(None) - .await? - .into_iter() - .filter(|u| u.lot == UserLot::Admin) - .collect_vec() - .len() - == 1 - && u.lot == UserLot::Admin - { - return Ok(false); - } - u.delete(&self.0.db).await?; - Ok(true) - } else { - Ok(false) + let Some(u) = maybe_user else { + return Ok(false); + }; + if self + .users_list(None) + .await? + .into_iter() + .filter(|u| u.lot == UserLot::Admin) + .collect_vec() + .len() + == 1 + && u.lot == UserLot::Admin + { + return Ok(false); } + u.delete(&self.0.db).await?; + Ok(true) } pub async fn register_user(&self, input: RegisterUserInput) -> Result { @@ -981,14 +980,13 @@ impl UserService { pub async fn user_details(&self, token: &str) -> Result { let found_token = user_id_from_token(token, &self.0.config.users.jwt_secret); - if let Ok(user_id) = found_token { - let user = user_by_id(&self.0.db, &user_id).await?; - Ok(UserDetailsResult::Ok(Box::new(user))) - } else { - Ok(UserDetailsResult::Error(UserDetailsError { + let Ok(user_id) = found_token else { + return Ok(UserDetailsResult::Error(UserDetailsError { error: UserDetailsErrorVariant::AuthTokenInvalid, - })) - } + })); + }; + let user = user_by_id(&self.0.db, &user_id).await?; + Ok(UserDetailsResult::Ok(Box::new(user))) } pub async fn user_integrations(&self, user_id: &String) -> Result> { diff --git a/crates/utils/database/src/lib.rs b/crates/utils/database/src/lib.rs index f40c62baa5..f290acc3ac 100644 --- a/crates/utils/database/src/lib.rs +++ b/crates/utils/database/src/lib.rs @@ -358,24 +358,23 @@ pub async fn check_token( db: &DatabaseConnection, ) -> Result { let claims = user_claims_from_token(token, jwt_secret)?; - if let Some(access_link) = claims.access_link { - let access_link = AccessLink::find_by_id(access_link.id) - .one(db) - .await? - .ok_or_else(|| Error::new(BackendError::SessionExpired.to_string()))?; - if access_link.is_revoked.unwrap_or_default() { - return Err(Error::new(BackendError::SessionExpired.to_string())); - } - if is_mutation { - if !access_link.is_mutation_allowed.unwrap_or_default() { - return Err(Error::new(BackendError::MutationNotAllowed.to_string())); - } - return Ok(true); + let Some(access_link) = claims.access_link else { + return Ok(true); + }; + let access_link = AccessLink::find_by_id(access_link.id) + .one(db) + .await? + .ok_or_else(|| Error::new(BackendError::SessionExpired.to_string()))?; + if access_link.is_revoked.unwrap_or_default() { + return Err(Error::new(BackendError::SessionExpired.to_string())); + } + if is_mutation { + if !access_link.is_mutation_allowed.unwrap_or_default() { + return Err(Error::new(BackendError::MutationNotAllowed.to_string())); } - Ok(true) - } else { - Ok(true) + return Ok(true); } + Ok(true) } pub async fn remove_entity_from_collection( From 95267bd3090186fd2740b7e3e631b6eee5b02c0e Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sat, 26 Oct 2024 11:01:44 +0530 Subject: [PATCH 14/30] refactor(backend): more if let Some --- crates/providers/src/manga_updates.rs | 5 ++-- crates/services/importer/src/goodreads.rs | 18 +++++------- crates/utils/dependent/src/lib.rs | 35 +++++++++++------------ 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/crates/providers/src/manga_updates.rs b/crates/providers/src/manga_updates.rs index f75d1b7117..bdb09d6edf 100644 --- a/crates/providers/src/manga_updates.rs +++ b/crates/providers/src/manga_updates.rs @@ -144,11 +144,10 @@ struct MetadataSearchResponse { impl MangaUpdatesService { fn extract_status(&self, input: Option) -> (Option, Option) { - if input.is_none() { + let Some(input) = input else { return (None, None); - } + }; - let input = input.unwrap(); let first_part = input.split("
").next().unwrap_or("").trim(); let parts: Vec<&str> = first_part.split_whitespace().collect(); diff --git a/crates/services/importer/src/goodreads.rs b/crates/services/importer/src/goodreads.rs index 970e006e13..3a13e863d8 100644 --- a/crates/services/importer/src/goodreads.rs +++ b/crates/services/importer/src/goodreads.rs @@ -116,22 +116,18 @@ pub async fn import( visibility: None, }); } - let mut reviews = vec![]; - if review.is_some() || rating.is_some() { - reviews.push(ImportOrExportItemRating { - review, - rating, - ..Default::default() - }); - } media.push(ImportOrExportMediaItem { - source_id: record.title.clone(), lot, source, identifier, - seen_history, - reviews, collections, + seen_history, + source_id: record.title.clone(), + reviews: vec![ImportOrExportItemRating { + review, + rating, + ..Default::default() + }], }); } else { failed_items.push(ImportFailedItem { diff --git a/crates/utils/dependent/src/lib.rs b/crates/utils/dependent/src/lib.rs index 867263597e..bc86c9d701 100644 --- a/crates/utils/dependent/src/lib.rs +++ b/crates/utils/dependent/src/lib.rs @@ -833,32 +833,31 @@ pub async fn commit_metadata( input: CommitMediaInput, ss: &Arc, ) -> Result { - if let Some(m) = Metadata::find() + let Some(m) = Metadata::find() .filter(metadata::Column::Lot.eq(input.lot)) .filter(metadata::Column::Source.eq(input.source)) .filter(metadata::Column::Identifier.eq(input.identifier.clone())) .one(&ss.db) .await? - { - let cached_metadata = CommitCache { - id: m.id.clone(), - lot: EntityLot::Metadata, - }; - if ss.commit_cache.get(&cached_metadata).await.is_some() { - return Ok(m); - } - ss.commit_cache.insert(cached_metadata.clone(), ()).await; - if input.force_update.unwrap_or_default() { - ryot_log!(debug, "Forcing update of metadata with id {}", &m.id); - update_metadata_and_notify_users(&m.id, true, ss).await?; - } - ss.commit_cache.remove(&cached_metadata).await; - Ok(m) - } else { + else { let details = details_from_provider(input.lot, input.source, &input.identifier, ss).await?; let media = commit_metadata_internal(details, None, ss).await?; - Ok(media) + return Ok(media); + }; + let cached_metadata = CommitCache { + id: m.id.clone(), + lot: EntityLot::Metadata, + }; + if ss.commit_cache.get(&cached_metadata).await.is_some() { + return Ok(m); + } + ss.commit_cache.insert(cached_metadata.clone(), ()).await; + if input.force_update.unwrap_or_default() { + ryot_log!(debug, "Forcing update of metadata with id {}", &m.id); + update_metadata_and_notify_users(&m.id, true, ss).await?; } + ss.commit_cache.remove(&cached_metadata).await; + Ok(m) } pub async fn deploy_update_metadata_job( From 7a4efb2e0b5e2ae8fdf4e642c951c2ce62c8d45a Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sat, 26 Oct 2024 11:32:14 +0530 Subject: [PATCH 15/30] refactor(frontend): move checkbox into common area --- apps/frontend/app/components/common.tsx | 41 +++++++++++-------- .../routes/_dashboard.media.$action.$lot.tsx | 17 +++----- .../_dashboard.media.groups.$action.tsx | 17 +++----- .../_dashboard.media.people.$action.tsx | 16 +++----- 4 files changed, 40 insertions(+), 51 deletions(-) diff --git a/apps/frontend/app/components/common.tsx b/apps/frontend/app/components/common.tsx index 9833047dff..f53a56c547 100644 --- a/apps/frontend/app/components/common.tsx +++ b/apps/frontend/app/components/common.tsx @@ -8,6 +8,7 @@ import { Badge, Box, Button, + Checkbox, Collapse, Divider, Flex, @@ -348,27 +349,35 @@ export const FiltersModal = (props: { export const CollectionsFilter = (props: { cookieName: string; collections?: string[]; + invertCollection?: boolean; }) => { const collections = useUserCollections(); const [_, { setP }] = useAppSearchParam(props.cookieName); return ( - ({ - value: c.id.toString(), - label: c.name, - })), - }, - ]} - onChange={(v) => setP("collections", v.join(","))} - clearable - searchable - /> + + ({ + value: c.id.toString(), + label: c.name, + })), + }, + ]} + onChange={(v) => setP("collections", v.join(","))} + clearable + searchable + /> + setP("invertCollection", String(e.target.checked))} + /> + ); }; diff --git a/apps/frontend/app/routes/_dashboard.media.$action.$lot.tsx b/apps/frontend/app/routes/_dashboard.media.$action.$lot.tsx index ad351d47ce..021e70bb4d 100644 --- a/apps/frontend/app/routes/_dashboard.media.$action.$lot.tsx +++ b/apps/frontend/app/routes/_dashboard.media.$action.$lot.tsx @@ -3,7 +3,6 @@ import { Box, Button, Center, - Checkbox, Container, Flex, Group, @@ -629,17 +628,11 @@ const FiltersModalForm = () => { )}
- - - setP("invertCollection", String(e.target.checked))} - /> - + ); }; diff --git a/apps/frontend/app/routes/_dashboard.media.groups.$action.tsx b/apps/frontend/app/routes/_dashboard.media.groups.$action.tsx index 485210de16..255b8455bb 100644 --- a/apps/frontend/app/routes/_dashboard.media.groups.$action.tsx +++ b/apps/frontend/app/routes/_dashboard.media.groups.$action.tsx @@ -2,7 +2,6 @@ import { ActionIcon, Box, Center, - Checkbox, Container, Flex, Group, @@ -417,17 +416,11 @@ const FiltersModalForm = () => { )} - - - setP("invertCollection", String(e.target.checked))} - /> - + ); }; diff --git a/apps/frontend/app/routes/_dashboard.media.people.$action.tsx b/apps/frontend/app/routes/_dashboard.media.people.$action.tsx index 3262a86e8f..0a2a991915 100644 --- a/apps/frontend/app/routes/_dashboard.media.people.$action.tsx +++ b/apps/frontend/app/routes/_dashboard.media.people.$action.tsx @@ -439,17 +439,11 @@ const FiltersModalForm = () => { )} - - - setP("invertCollection", String(e.target.checked))} - /> - + ); }; From 13242d12b817e64d14136c526fe925c51a05812d Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sat, 26 Oct 2024 11:38:25 +0530 Subject: [PATCH 16/30] fix(frontend): position of invert btn --- apps/frontend/app/components/common.tsx | 50 +++++++++++++------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/apps/frontend/app/components/common.tsx b/apps/frontend/app/components/common.tsx index f53a56c547..0ce776e331 100644 --- a/apps/frontend/app/components/common.tsx +++ b/apps/frontend/app/components/common.tsx @@ -23,6 +23,7 @@ import { TextInput, Title, Tooltip, + rem, } from "@mantine/core"; import { useDebouncedValue, useDidUpdate, useDisclosure } from "@mantine/hooks"; import { @@ -355,29 +356,32 @@ export const CollectionsFilter = (props: { const [_, { setP }] = useAppSearchParam(props.cookieName); return ( - - ({ - value: c.id.toString(), - label: c.name, - })), - }, - ]} - onChange={(v) => setP("collections", v.join(","))} - clearable - searchable - /> - setP("invertCollection", String(e.target.checked))} - /> - + setP("collections", v.join(","))} + data={[ + { + group: "My collections", + items: collections.map((c) => ({ + value: c.id.toString(), + label: c.name, + })), + }, + ]} + rightSection={ + setP("invertCollection", String(e.target.checked))} + /> + } + /> ); }; From 9c5079df205135581ae630baeaf01c4cb4266d27 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sun, 27 Oct 2024 01:03:45 +0530 Subject: [PATCH 17/30] chore(frontend): use inbuilt function --- apps/frontend/app/routes/_dashboard._index.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/frontend/app/routes/_dashboard._index.tsx b/apps/frontend/app/routes/_dashboard._index.tsx index bbaba20db5..dfdd0a9cb9 100644 --- a/apps/frontend/app/routes/_dashboard._index.tsx +++ b/apps/frontend/app/routes/_dashboard._index.tsx @@ -34,6 +34,7 @@ import { } from "@ryot/generated/graphql/backend/graphql"; import { changeCase, + formatDateToNaiveDate, humanizeDuration, isBoolean, isNumber, @@ -632,8 +633,10 @@ const ActivitySection = () => { const end = now.endOf("day"); const startDate = getDateFromTimeSpan(timeSpan); return { - startDate: startDate?.format("YYYY-MM-DD"), - endDate: end.format("YYYY-MM-DD"), + startDate: startDate + ? formatDateToNaiveDate(startDate.toDate()) + : undefined, + endDate: formatDateToNaiveDate(end.toDate()), }; }, [timeSpan]); const { data: dailyUserActivitiesData } = useQuery({ From 3dc9f81b0be748513866ad260a9ac2f7f9a0c72f Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sun, 27 Oct 2024 01:09:16 +0530 Subject: [PATCH 18/30] ci: Run CI From d20da1ab13104d65b4a47f2ef32bc5b48b2f51cc Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sun, 27 Oct 2024 09:25:00 +0530 Subject: [PATCH 19/30] feat(backend): return more core details --- Cargo.lock | 2 ++ crates/config/Cargo.toml | 1 + crates/config/src/lib.rs | 5 +-- crates/models/dependent/Cargo.toml | 1 + crates/models/dependent/src/lib.rs | 6 ++-- crates/services/miscellaneous/src/lib.rs | 5 +-- libs/generated/src/graphql/backend/gql.ts | 4 +-- libs/generated/src/graphql/backend/graphql.ts | 31 +++++++++++++++++-- .../src/backend/queries/CoreDetails.gql | 15 +++++++-- 9 files changed, 57 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 487f7a7d3e..a525f40bf6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1392,6 +1392,7 @@ name = "config" version = "0.1.0" dependencies = [ "anyhow", + "async-graphql", "common-utils", "env-utils", "schematic", @@ -1741,6 +1742,7 @@ version = "0.1.0" dependencies = [ "async-graphql", "common-models", + "config", "database-models", "enums", "fitness-models", diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 60c21dca7e..447cce02af 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] anyhow = { workspace = true } +async-graphql = { workspace = true } common-utils = { path = "../utils/common" } env-utils = { path = "../utils/env" } schematic = { workspace = true } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 3c01764fff..2f52ff59fe 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1,6 +1,7 @@ use std::path::PathBuf; use anyhow::Result; +use async_graphql::SimpleObject; use common_utils::{IsFeatureEnabled, PROJECT_NAME}; use env_utils::{DEFAULT_MAL_CLIENT_ID, DEFAULT_TMDB_ACCESS_TOKEN}; use schematic::{derive_enum, validate::not_empty, Config, ConfigEnum, ConfigLoader, HandlerError}; @@ -278,7 +279,7 @@ impl IsFeatureEnabled for FileStorageConfig { /// The configuration related to Umami analytics. More information /// [here](https://umami.is/docs/tracker-configuration). -#[derive(Debug, Serialize, Deserialize, Clone, Config)] +#[derive(Debug, Serialize, Deserialize, Clone, Config, SimpleObject)] #[config(rename_all = "snake_case", env_prefix = "FRONTEND_UMAMI_")] pub struct FrontendUmamiConfig { /// For example: https://umami.is/script.js. @@ -287,7 +288,7 @@ pub struct FrontendUmamiConfig { pub domains: String, } -#[derive(Debug, Serialize, Deserialize, Clone, Config)] +#[derive(Debug, Serialize, Deserialize, Clone, Config, SimpleObject)] #[config(rename_all = "snake_case", env_prefix = "FRONTEND_")] pub struct FrontendConfig { /// Used as the base URL when generating item links for the frontend. diff --git a/crates/models/dependent/Cargo.toml b/crates/models/dependent/Cargo.toml index 8b8ad87f16..21a3c07004 100644 --- a/crates/models/dependent/Cargo.toml +++ b/crates/models/dependent/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] async-graphql = { workspace = true } common-models = { path = "../common" } +config = { path = "../../config" } database-models = { path = "../database" } enums = { path = "../../enums" } importer-models = { path = "../importer" } diff --git a/crates/models/dependent/src/lib.rs b/crates/models/dependent/src/lib.rs index 89d574a968..0ecbc80433 100644 --- a/crates/models/dependent/src/lib.rs +++ b/crates/models/dependent/src/lib.rs @@ -1,5 +1,6 @@ use async_graphql::{InputObject, OutputType, SimpleObject, Union}; use common_models::{BackendError, SearchDetails}; +use config::FrontendConfig; use database_models::{ collection, exercise, metadata, metadata_group, person, seen, user, user_measurement, user_to_entity, workout, workout_template, @@ -149,17 +150,18 @@ pub struct MetadataBaseData { #[derive(Debug, SimpleObject, Serialize, Deserialize)] pub struct CoreDetails { pub is_pro: bool, - pub page_limit: i32, pub version: String, pub docs_link: String, pub oidc_enabled: bool, pub smtp_enabled: bool, pub website_url: String, pub signup_allowed: bool, + pub disable_telemetry: bool, pub repository_link: String, + pub frontend: FrontendConfig, pub token_valid_for_days: i32, - pub file_storage_enabled: bool, pub local_auth_disabled: bool, + pub file_storage_enabled: bool, pub backend_errors: Vec, } diff --git a/crates/services/miscellaneous/src/lib.rs b/crates/services/miscellaneous/src/lib.rs index b0f0b9cd75..a234a5970d 100644 --- a/crates/services/miscellaneous/src/lib.rs +++ b/crates/services/miscellaneous/src/lib.rs @@ -246,16 +246,17 @@ ORDER BY RANDOM() LIMIT 10; is_pro: self.0.is_pro, version: APP_VERSION.to_owned(), file_storage_enabled: files_enabled, + frontend: self.0.config.frontend.clone(), website_url: "https://ryot.io".to_owned(), oidc_enabled: self.0.oidc_client.is_some(), - page_limit: self.0.config.frontend.page_size, docs_link: "https://docs.ryot.io".to_owned(), backend_errors: BackendError::iter().collect(), + disable_telemetry: self.0.config.disable_telemetry, smtp_enabled: self.0.config.server.smtp.is_enabled(), signup_allowed: self.0.config.users.allow_registration, local_auth_disabled: self.0.config.users.disable_local_auth, - token_valid_for_days: self.0.config.users.token_valid_for_days, repository_link: "https://github.com/ignisda/ryot".to_owned(), + token_valid_for_days: self.0.config.users.token_valid_for_days, } } diff --git a/libs/generated/src/graphql/backend/gql.ts b/libs/generated/src/graphql/backend/gql.ts index 3768873a2a..27b460e91e 100644 --- a/libs/generated/src/graphql/backend/gql.ts +++ b/libs/generated/src/graphql/backend/gql.ts @@ -15,7 +15,7 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/ const documents = { "mutation RegisterUser($input: RegisterUserInput!) {\n registerUser(input: $input) {\n __typename\n ... on RegisterError {\n error\n }\n ... on StringIdObject {\n id\n }\n }\n}\n\nmutation LoginUser($input: AuthUserInput!) {\n loginUser(input: $input) {\n __typename\n ... on LoginError {\n error\n }\n ... on LoginResponse {\n apiKey\n }\n }\n}\n\nmutation AddEntityToCollection($input: ChangeCollectionToEntityInput!) {\n addEntityToCollection(input: $input)\n}\n\nmutation CommitMetadata($input: CommitMediaInput!) {\n commitMetadata(input: $input) {\n id\n }\n}\n\nmutation CommitMetadataGroup($input: CommitMediaInput!) {\n commitMetadataGroup(input: $input) {\n id\n }\n}\n\nmutation CommitPerson($input: CommitPersonInput!) {\n commitPerson(input: $input) {\n id\n }\n}\n\nmutation CreateCustomExercise($input: ExerciseInput!) {\n createCustomExercise(input: $input)\n}\n\nmutation UpdateCustomExercise($input: UpdateCustomExerciseInput!) {\n updateCustomExercise(input: $input)\n}\n\nmutation UpdateUserIntegration($input: UpdateUserIntegrationInput!) {\n updateUserIntegration(input: $input)\n}\n\nmutation CreateCustomMetadata($input: CreateCustomMetadataInput!) {\n createCustomMetadata(input: $input) {\n id\n }\n}\n\nmutation CreateOrUpdateCollection($input: CreateOrUpdateCollectionInput!) {\n createOrUpdateCollection(input: $input) {\n id\n }\n}\n\nmutation CreateReviewComment($input: CreateReviewCommentInput!) {\n createReviewComment(input: $input)\n}\n\nmutation CreateUserMeasurement($input: UserMeasurementInput!) {\n createUserMeasurement(input: $input)\n}\n\nmutation CreateUserNotificationPlatform($input: CreateUserNotificationPlatformInput!) {\n createUserNotificationPlatform(input: $input)\n}\n\nmutation CreateUserIntegration($input: CreateUserIntegrationInput!) {\n createUserIntegration(input: $input) {\n id\n }\n}\n\nmutation CreateOrUpdateUserWorkout($input: UserWorkoutInput!) {\n createOrUpdateUserWorkout(input: $input)\n}\n\nmutation CreateOrUpdateUserWorkoutTemplate($input: UserWorkoutInput!) {\n createOrUpdateUserWorkoutTemplate(input: $input)\n}\n\nmutation DeleteCollection($collectionName: String!) {\n deleteCollection(collectionName: $collectionName)\n}\n\nmutation DeleteReview($reviewId: String!) {\n deleteReview(reviewId: $reviewId)\n}\n\nmutation DeleteS3Object($key: String!) {\n deleteS3Object(key: $key)\n}\n\nmutation DeleteSeenItem($seenId: String!) {\n deleteSeenItem(seenId: $seenId) {\n id\n }\n}\n\nmutation DeleteUser($toDeleteUserId: String!) {\n deleteUser(toDeleteUserId: $toDeleteUserId)\n}\n\nmutation DeleteUserIntegration($integrationId: String!) {\n deleteUserIntegration(integrationId: $integrationId)\n}\n\nmutation DeleteUserMeasurement($timestamp: DateTime!) {\n deleteUserMeasurement(timestamp: $timestamp)\n}\n\nmutation DeleteUserNotificationPlatform($notificationId: String!) {\n deleteUserNotificationPlatform(notificationId: $notificationId)\n}\n\nmutation DeleteUserWorkout($workoutId: String!) {\n deleteUserWorkout(workoutId: $workoutId)\n}\n\nmutation DeleteUserWorkoutTemplate($workoutTemplateId: String!) {\n deleteUserWorkoutTemplate(workoutTemplateId: $workoutTemplateId)\n}\n\nmutation DeployBackgroundJob($jobName: BackgroundJob!) {\n deployBackgroundJob(jobName: $jobName)\n}\n\nmutation DeployBulkProgressUpdate($input: [ProgressUpdateInput!]!) {\n deployBulkProgressUpdate(input: $input)\n}\n\nmutation DeployExportJob {\n deployExportJob\n}\n\nmutation DeployImportJob($input: DeployImportJobInput!) {\n deployImportJob(input: $input)\n}\n\nmutation DeployUpdateMetadataJob($metadataId: String!) {\n deployUpdateMetadataJob(metadataId: $metadataId)\n}\n\nmutation DeployUpdatePersonJob($personId: String!) {\n deployUpdatePersonJob(personId: $personId)\n}\n\nmutation DeployUpdateMetadataGroupJob($metadataGroupId: String!) {\n deployUpdateMetadataGroupJob(metadataGroupId: $metadataGroupId)\n}\n\nmutation UpdateSeenItem($input: UpdateSeenItemInput!) {\n updateSeenItem(input: $input)\n}\n\nmutation UpdateUserNotificationPlatform($input: UpdateUserNotificationPlatformInput!) {\n updateUserNotificationPlatform(input: $input)\n}\n\nmutation UpdateUserWorkoutAttributes($input: UpdateUserWorkoutAttributesInput!) {\n updateUserWorkoutAttributes(input: $input)\n}\n\nmutation GenerateAuthToken {\n generateAuthToken\n}\n\nmutation MergeMetadata($mergeFrom: String!, $mergeInto: String!) {\n mergeMetadata(mergeFrom: $mergeFrom, mergeInto: $mergeInto)\n}\n\nmutation DisassociateMetadata($metadataId: String!) {\n disassociateMetadata(metadataId: $metadataId)\n}\n\nmutation CreateOrUpdateReview($input: CreateOrUpdateReviewInput!) {\n createOrUpdateReview(input: $input) {\n id\n }\n}\n\nmutation PresignedPutS3Url($input: PresignedPutUrlInput!) {\n presignedPutS3Url(input: $input) {\n key\n uploadUrl\n }\n}\n\nmutation RemoveEntityFromCollection($input: ChangeCollectionToEntityInput!) {\n removeEntityFromCollection(input: $input) {\n id\n }\n}\n\nmutation TestUserNotificationPlatforms {\n testUserNotificationPlatforms\n}\n\nmutation UpdateUser($input: UpdateUserInput!) {\n updateUser(input: $input) {\n id\n }\n}\n\nmutation UpdateUserPreference($input: UpdateComplexJsonInput!) {\n updateUserPreference(input: $input)\n}\n\nmutation CreateAccessLink($input: CreateAccessLinkInput!) {\n createAccessLink(input: $input) {\n id\n }\n}\n\nmutation ProcessAccessLink($input: ProcessAccessLinkInput!) {\n processAccessLink(input: $input) {\n __typename\n ... on ProcessAccessLinkError {\n error\n }\n ... on ProcessAccessLinkResponse {\n apiKey\n redirectTo\n tokenValidForDays\n }\n }\n}\n\nmutation RevokeAccessLink($accessLinkId: String!) {\n revokeAccessLink(accessLinkId: $accessLinkId)\n}\n\nmutation UpdateUserExerciseSettings($input: UpdateUserExerciseSettings!) {\n updateUserExerciseSettings(input: $input)\n}": types.RegisterUserDocument, "query CollectionContents($input: CollectionContentsInput!) {\n collectionContents(input: $input) {\n user {\n id\n name\n }\n reviews {\n ...ReviewItemPart\n }\n results {\n details {\n total\n nextPage\n }\n items {\n entityId\n entityLot\n }\n }\n details {\n name\n description\n createdOn\n }\n }\n}": types.CollectionContentsDocument, - "query CoreDetails {\n coreDetails {\n isPro\n version\n docsLink\n pageLimit\n websiteUrl\n smtpEnabled\n oidcEnabled\n signupAllowed\n repositoryLink\n localAuthDisabled\n fileStorageEnabled\n tokenValidForDays\n }\n}": types.CoreDetailsDocument, + "query CoreDetails {\n coreDetails {\n isPro\n version\n docsLink\n websiteUrl\n smtpEnabled\n oidcEnabled\n signupAllowed\n repositoryLink\n disableTelemetry\n tokenValidForDays\n localAuthDisabled\n fileStorageEnabled\n frontend {\n url\n pageSize\n oidcButtonLabel\n dashboardMessage\n umami {\n domains\n scriptUrl\n websiteId\n }\n }\n }\n}": types.CoreDetailsDocument, "query ExerciseDetails($exerciseId: String!) {\n exerciseDetails(exerciseId: $exerciseId) {\n id\n lot\n source\n level\n force\n mechanic\n equipment\n muscles\n createdByUserId\n attributes {\n instructions\n images\n }\n }\n}": types.ExerciseDetailsDocument, "query ExerciseParameters {\n exerciseParameters {\n downloadRequired\n filters {\n type\n level\n force\n mechanic\n equipment\n muscle\n }\n lotMapping {\n lot\n bests\n }\n }\n}": types.ExerciseParametersDocument, "query ExercisesList($input: ExercisesListInput!) {\n exercisesList(input: $input) {\n details {\n total\n nextPage\n }\n items {\n id\n lot\n image\n muscle\n numTimesInteracted\n lastUpdatedOn\n }\n }\n}": types.ExercisesListDocument, @@ -70,7 +70,7 @@ export function graphql(source: "query CollectionContents($input: CollectionCont /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query CoreDetails {\n coreDetails {\n isPro\n version\n docsLink\n pageLimit\n websiteUrl\n smtpEnabled\n oidcEnabled\n signupAllowed\n repositoryLink\n localAuthDisabled\n fileStorageEnabled\n tokenValidForDays\n }\n}"): (typeof documents)["query CoreDetails {\n coreDetails {\n isPro\n version\n docsLink\n pageLimit\n websiteUrl\n smtpEnabled\n oidcEnabled\n signupAllowed\n repositoryLink\n localAuthDisabled\n fileStorageEnabled\n tokenValidForDays\n }\n}"]; +export function graphql(source: "query CoreDetails {\n coreDetails {\n isPro\n version\n docsLink\n websiteUrl\n smtpEnabled\n oidcEnabled\n signupAllowed\n repositoryLink\n disableTelemetry\n tokenValidForDays\n localAuthDisabled\n fileStorageEnabled\n frontend {\n url\n pageSize\n oidcButtonLabel\n dashboardMessage\n umami {\n domains\n scriptUrl\n websiteId\n }\n }\n }\n}"): (typeof documents)["query CoreDetails {\n coreDetails {\n isPro\n version\n docsLink\n websiteUrl\n smtpEnabled\n oidcEnabled\n signupAllowed\n repositoryLink\n disableTelemetry\n tokenValidForDays\n localAuthDisabled\n fileStorageEnabled\n frontend {\n url\n pageSize\n oidcButtonLabel\n dashboardMessage\n umami {\n domains\n scriptUrl\n websiteId\n }\n }\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/libs/generated/src/graphql/backend/graphql.ts b/libs/generated/src/graphql/backend/graphql.ts index 72e7598d5d..503529edb5 100644 --- a/libs/generated/src/graphql/backend/graphql.ts +++ b/libs/generated/src/graphql/backend/graphql.ts @@ -218,12 +218,13 @@ export type CommitPersonInput = { export type CoreDetails = { backendErrors: Array; + disableTelemetry: Scalars['Boolean']['output']; docsLink: Scalars['String']['output']; fileStorageEnabled: Scalars['Boolean']['output']; + frontend: FrontendConfig; isPro: Scalars['Boolean']['output']; localAuthDisabled: Scalars['Boolean']['output']; oidcEnabled: Scalars['Boolean']['output']; - pageLimit: Scalars['Int']['output']; repositoryLink: Scalars['String']['output']; signupAllowed: Scalars['Boolean']['output']; smtpEnabled: Scalars['Boolean']['output']; @@ -640,6 +641,30 @@ export type ExportJob = { url: Scalars['String']['output']; }; +export type FrontendConfig = { + /** A message to be displayed on the dashboard. */ + dashboardMessage: Scalars['String']['output']; + /** The button label for OIDC authentication. */ + oidcButtonLabel: Scalars['String']['output']; + /** The number of items to display in a list view. */ + pageSize: Scalars['Int']['output']; + /** Settings related to Umami analytics. */ + umami: FrontendUmamiConfig; + /** Used as the base URL when generating item links for the frontend. */ + url: Scalars['String']['output']; +}; + +/** + * The configuration related to Umami analytics. More information + * [here](https://umami.is/docs/tracker-configuration). + */ +export type FrontendUmamiConfig = { + domains: Scalars['String']['output']; + /** For example: https://umami.is/script.js. */ + scriptUrl: Scalars['String']['output']; + websiteId: Scalars['String']['output']; +}; + export type GenreDetails = { contents: IdResults; details: GenreListItem; @@ -3045,7 +3070,7 @@ export type CollectionContentsQuery = { collectionContents: { user: { id: string export type CoreDetailsQueryVariables = Exact<{ [key: string]: never; }>; -export type CoreDetailsQuery = { coreDetails: { isPro: boolean, version: string, docsLink: string, pageLimit: number, websiteUrl: string, smtpEnabled: boolean, oidcEnabled: boolean, signupAllowed: boolean, repositoryLink: string, localAuthDisabled: boolean, fileStorageEnabled: boolean, tokenValidForDays: number } }; +export type CoreDetailsQuery = { coreDetails: { isPro: boolean, version: string, docsLink: string, websiteUrl: string, smtpEnabled: boolean, oidcEnabled: boolean, signupAllowed: boolean, repositoryLink: string, disableTelemetry: boolean, tokenValidForDays: number, localAuthDisabled: boolean, fileStorageEnabled: boolean, frontend: { url: string, pageSize: number, oidcButtonLabel: string, dashboardMessage: string, umami: { domains: string, scriptUrl: string, websiteId: string } } } }; export type ExerciseDetailsQueryVariables = Exact<{ exerciseId: Scalars['String']['input']; @@ -3423,7 +3448,7 @@ export const ProcessAccessLinkDocument = {"kind":"Document","definitions":[{"kin export const RevokeAccessLinkDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RevokeAccessLink"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"accessLinkId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"revokeAccessLink"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"accessLinkId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"accessLinkId"}}}]}]}}]} as unknown as DocumentNode; export const UpdateUserExerciseSettingsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateUserExerciseSettings"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateUserExerciseSettings"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateUserExerciseSettings"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; export const CollectionContentsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CollectionContents"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CollectionContentsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"collectionContents"},"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":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"results"},"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"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entityId"}},{"kind":"Field","name":{"kind":"Name","value":"entityLot"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}}]}}]}}]}},{"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":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"chapter"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ReviewItemPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ReviewItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rating"}},{"kind":"Field","name":{"kind":"Name","value":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"seenItemsAssociatedWith"}},{"kind":"Field","name":{"kind":"Name","value":"postedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"text"}},{"kind":"Field","name":{"kind":"Name","value":"likedBy"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"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"}}]}}]}}]} as unknown as DocumentNode; -export const CoreDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CoreDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"coreDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"isPro"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"docsLink"}},{"kind":"Field","name":{"kind":"Name","value":"pageLimit"}},{"kind":"Field","name":{"kind":"Name","value":"websiteUrl"}},{"kind":"Field","name":{"kind":"Name","value":"smtpEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"oidcEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"signupAllowed"}},{"kind":"Field","name":{"kind":"Name","value":"repositoryLink"}},{"kind":"Field","name":{"kind":"Name","value":"localAuthDisabled"}},{"kind":"Field","name":{"kind":"Name","value":"fileStorageEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"tokenValidForDays"}}]}}]}}]} as unknown as DocumentNode; +export const CoreDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CoreDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"coreDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"isPro"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"docsLink"}},{"kind":"Field","name":{"kind":"Name","value":"websiteUrl"}},{"kind":"Field","name":{"kind":"Name","value":"smtpEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"oidcEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"signupAllowed"}},{"kind":"Field","name":{"kind":"Name","value":"repositoryLink"}},{"kind":"Field","name":{"kind":"Name","value":"disableTelemetry"}},{"kind":"Field","name":{"kind":"Name","value":"tokenValidForDays"}},{"kind":"Field","name":{"kind":"Name","value":"localAuthDisabled"}},{"kind":"Field","name":{"kind":"Name","value":"fileStorageEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"frontend"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"pageSize"}},{"kind":"Field","name":{"kind":"Name","value":"oidcButtonLabel"}},{"kind":"Field","name":{"kind":"Name","value":"dashboardMessage"}},{"kind":"Field","name":{"kind":"Name","value":"umami"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"domains"}},{"kind":"Field","name":{"kind":"Name","value":"scriptUrl"}},{"kind":"Field","name":{"kind":"Name","value":"websiteId"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const ExerciseDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ExerciseDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"exerciseId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"exerciseDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"exerciseId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"exerciseId"}}}],"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":"level"}},{"kind":"Field","name":{"kind":"Name","value":"force"}},{"kind":"Field","name":{"kind":"Name","value":"mechanic"}},{"kind":"Field","name":{"kind":"Name","value":"equipment"}},{"kind":"Field","name":{"kind":"Name","value":"muscles"}},{"kind":"Field","name":{"kind":"Name","value":"createdByUserId"}},{"kind":"Field","name":{"kind":"Name","value":"attributes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"instructions"}},{"kind":"Field","name":{"kind":"Name","value":"images"}}]}}]}}]}}]} as unknown as DocumentNode; export const ExerciseParametersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ExerciseParameters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"exerciseParameters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"downloadRequired"}},{"kind":"Field","name":{"kind":"Name","value":"filters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"level"}},{"kind":"Field","name":{"kind":"Name","value":"force"}},{"kind":"Field","name":{"kind":"Name","value":"mechanic"}},{"kind":"Field","name":{"kind":"Name","value":"equipment"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lotMapping"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"bests"}}]}}]}}]}}]} as unknown as DocumentNode; export const ExercisesListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ExercisesList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ExercisesListInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"exercisesList"},"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"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}},{"kind":"Field","name":{"kind":"Name","value":"numTimesInteracted"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedOn"}}]}}]}}]}}]} as unknown as DocumentNode; diff --git a/libs/graphql/src/backend/queries/CoreDetails.gql b/libs/graphql/src/backend/queries/CoreDetails.gql index c7dd702298..c53cebada4 100644 --- a/libs/graphql/src/backend/queries/CoreDetails.gql +++ b/libs/graphql/src/backend/queries/CoreDetails.gql @@ -3,14 +3,25 @@ query CoreDetails { isPro version docsLink - pageLimit websiteUrl smtpEnabled oidcEnabled signupAllowed repositoryLink + disableTelemetry + tokenValidForDays localAuthDisabled fileStorageEnabled - tokenValidForDays + frontend { + url + pageSize + oidcButtonLabel + dashboardMessage + umami { + domains + scriptUrl + websiteId + } + } } } From 63c7a864a0f25653f10937533a59758495cd976c Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sun, 27 Oct 2024 09:31:42 +0530 Subject: [PATCH 20/30] chore(frontend): do not parse env variables in the frontend --- apps/frontend/app/lib/utilities.server.ts | 45 ++++++------------- .../frontend/app/routes/_dashboard._index.tsx | 4 +- apps/frontend/app/routes/_dashboard.tsx | 26 +++++------ apps/frontend/app/routes/auth.tsx | 7 ++- 4 files changed, 29 insertions(+), 53 deletions(-) diff --git a/apps/frontend/app/lib/utilities.server.ts b/apps/frontend/app/lib/utilities.server.ts index 6b44bf03ab..832e96610f 100644 --- a/apps/frontend/app/lib/utilities.server.ts +++ b/apps/frontend/app/lib/utilities.server.ts @@ -100,16 +100,14 @@ export const serverGqlService = new AuthenticatedGraphQLClient( { headers: { Connection: "keep-alive" } }, ); -export const getCookieValue = (request: Request, cookieName: string) => { - return parse(request.headers.get("cookie") || "")[cookieName]; -}; +export const getCookieValue = (request: Request, cookieName: string) => + parse(request.headers.get("cookie") || "")[cookieName]; -export const getAuthorizationCookie = (request: Request) => { - return getCookieValue(request, AUTH_COOKIE_NAME); -}; +export const getAuthorizationCookie = (request: Request) => + getCookieValue(request, AUTH_COOKIE_NAME); export const redirectIfNotAuthenticatedOrUpdated = async (request: Request) => { - const { userDetails } = await getCachedUserDetails(request); + const userDetails = await getCachedUserDetails(request); if (!userDetails || userDetails.__typename === "UserDetailsError") { const nextUrl = withoutHost(request.url); throw redirect($path("/auth", { [redirectToQueryParam]: nextUrl }), { @@ -126,20 +124,6 @@ export const redirectIfNotAuthenticatedOrUpdated = async (request: Request) => { return userDetails; }; -const expectedServerVariables = z.object({ - DISABLE_TELEMETRY: z - .string() - .optional() - .transform((v) => v === "true"), - FRONTEND_UMAMI_SCRIPT_URL: z.string().optional(), - FRONTEND_UMAMI_WEBSITE_ID: z.string().optional(), - FRONTEND_UMAMI_DOMAINS: z.string().optional(), - FRONTEND_OIDC_BUTTON_LABEL: z - .string() - .default("Continue with OpenID Connect"), - FRONTEND_DASHBOARD_MESSAGE: z.string().optional(), -}); - /** * Combine multiple header objects into one (uses append so headers are not overridden) */ @@ -169,7 +153,8 @@ export const MetadataSpecificsSchema = z.object({ export const getCachedCoreDetails = async () => { return await queryClient.ensureQueryData({ queryKey: queryFactory.miscellaneous.coreDetails().queryKey, - queryFn: () => serverGqlService.request(CoreDetailsDocument), + queryFn: () => + serverGqlService.request(CoreDetailsDocument).then((d) => d.coreDetails), }); }; @@ -178,11 +163,9 @@ export const getCachedUserDetails = async (request: Request) => { return await queryClient.ensureQueryData({ queryKey: queryFactory.users.details(token ?? "").queryKey, queryFn: () => - serverGqlService.authenticatedRequest( - request, - UserDetailsDocument, - undefined, - ), + serverGqlService + .authenticatedRequest(request, UserDetailsDocument, undefined) + .then((d) => d.userDetails), }); }; @@ -294,8 +277,6 @@ export const s3FileUploader = (prefix: string) => return undefined; }, unstable_createMemoryUploadHandler()); -export const serverVariables = expectedServerVariables.parse(process.env); - export const toastSessionStorage = createCookieSessionStorage({ cookie: { sameSite: "lax", @@ -363,7 +344,7 @@ export const getCookiesForApplication = async ( token: string, tokenValidForDays?: number, ) => { - const [{ coreDetails }] = await Promise.all([getCachedCoreDetails()]); + const [coreDetails] = await Promise.all([getCachedCoreDetails()]); const maxAge = (tokenValidForDays || coreDetails.tokenValidForDays) * 24 * 60 * 60; const options = { maxAge, path: "/" } satisfies SerializeOptions; @@ -422,9 +403,9 @@ export const redirectToFirstPageIfOnInvalidPage = async ( totalResults: number, currentPage: number, ) => { - const { coreDetails } = await getCachedCoreDetails(); + const coreDetails = await getCachedCoreDetails(); const { searchParams } = new URL(request.url); - const totalPages = Math.ceil(totalResults / coreDetails.pageLimit); + const totalPages = Math.ceil(totalResults / coreDetails.frontend.pageSize); if (currentPage > totalPages && currentPage !== 1) { searchParams.set(pageQueryParam, "1"); throw redirect(`?${searchParams.toString()}`); diff --git a/apps/frontend/app/routes/_dashboard._index.tsx b/apps/frontend/app/routes/_dashboard._index.tsx index dfdd0a9cb9..16e74c9754 100644 --- a/apps/frontend/app/routes/_dashboard._index.tsx +++ b/apps/frontend/app/routes/_dashboard._index.tsx @@ -178,9 +178,9 @@ export default function Page() { return ( - {dashboardLayoutData.envData.FRONTEND_DASHBOARD_MESSAGE ? ( + {dashboardLayoutData.coreDetails.frontend.dashboardMessage ? ( }> - {dashboardLayoutData.envData.FRONTEND_DASHBOARD_MESSAGE} + {dashboardLayoutData.coreDetails.frontend.dashboardMessage} ) : null} {userPreferences.general.dashboard.map((de) => diff --git a/apps/frontend/app/routes/_dashboard.tsx b/apps/frontend/app/routes/_dashboard.tsx index f686c5bbd2..04fc27c855 100644 --- a/apps/frontend/app/routes/_dashboard.tsx +++ b/apps/frontend/app/routes/_dashboard.tsx @@ -134,7 +134,6 @@ import { useReviewEntity, } from "~/lib/state/media"; import { - serverVariables as envData, getAuthorizationCookie, getCachedCoreDetails, getCachedUserCollectionsList, @@ -152,13 +151,11 @@ const discordLink = "https://discord.gg/D9XTg2a7R8"; export const loader = async ({ request }: LoaderFunctionArgs) => { const userDetails = await redirectIfNotAuthenticatedOrUpdated(request); - const [userPreferences, userCollections, { coreDetails }] = await Promise.all( - [ - getCachedUserPreferences(request), - getCachedUserCollectionsList(request), - getCachedCoreDetails(), - ], - ); + const [userPreferences, userCollections, coreDetails] = await Promise.all([ + getCachedUserPreferences(request), + getCachedUserCollectionsList(request), + getCachedCoreDetails(), + ]); const mediaLinks = [ ...(Object.entries(userPreferences.featuresEnabled.media || {}) @@ -250,16 +247,15 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { const isDemo = Boolean(decodedCookie?.access_link?.is_demo); const shouldHaveUmami = - envData.FRONTEND_UMAMI_SCRIPT_URL && - envData.FRONTEND_UMAMI_WEBSITE_ID && - !envData.DISABLE_TELEMETRY && + coreDetails.frontend.umami.scriptUrl && + coreDetails.frontend.umami.websiteId && + !coreDetails.disableTelemetry && !isDemo; const workoutInProgress = isWorkoutActive(request); return { isDemo, - envData, mediaLinks, userDetails, coreDetails, @@ -762,9 +758,9 @@ export default function Layout() { {loaderData.shouldHaveUmami ? (