Repository: IgnisDa/ryot (language: rust)

Issue:
Anime imported from Anilist are sometimes marked as completed even when not all episodes were watched and the dropped/on hold actions do not appear
![image](https://github.com/user-attachments/assets/672fc654-4eb0-4e09-9f85-1d6a5befac03)

This anime was imported from Anilist as "completed" by me, even though i had only watched an episode.

Also, it may be an entirely separate issue, but none of the anime in my collection show the actions for putting the media on hold or dropping it, whether they're listed as completed or not.

![image](https://github.com/user-attachments/assets/16b399c7-c8c4-48c7-8b60-239d778b07fe)
![image](https://github.com/user-attachments/assets/51bcdcb5-cfb0-4ddc-8353-fd15d31d8cbb)

Lastly, the term "listened" is used in the action, when I think "watched" makes more sense for anime.


Discussion / hints:
Note to self: 
- ryot deploys jobs to update metadata, group and person details.
- ryot commits the progress updates after all the above jobs have deployed.

before step 2, i need to add an additional step that iterates over all the metadata items, check if `is_partial` has been set to `false or null`. if yes, then add it to a `HashSet (a)`. If not, then increment the count in a separate `HashMap (b)`, wait for 10 seconds and continue. Keep iterating until all the metadata to be imported are in `a` or their associated count in `b` has reached 5. 

edit: A single `HashMap<String, Either<bool, i32>>` should be sufficient for the above.
@gabeklavans Would it be possible for you to DM me your anilist export file. I need to account for edge cases as well.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 83a507ef59..ab1e344c82 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1766,13 +1766,16 @@ dependencies = [
  "importer-models",
  "itertools 0.13.0",
  "media-models",
+ "migrations",
  "nanoid",
  "providers",
+ "rand 0.9.0-beta.0",
  "rust_decimal",
  "rust_decimal_macros",
  "sea-orm",
  "serde",
  "serde_json",
+ "slug",
  "sqlx",
  "supporting-service",
  "tracing",
@@ -2235,6 +2238,7 @@ dependencies = [
  "database-models",
  "database-utils",
  "dependent-models",
+ "dependent-utils",
  "enum-models",
  "fitness-models",
  "itertools 0.13.0",
@@ -2247,6 +2251,7 @@ dependencies = [
  "supporting-service",
  "tokio",
  "tokio-util",
+ "tracing",
 ]
 
 [[package]]
@@ -2381,12 +2386,10 @@ dependencies = [
  "enum-models",
  "fitness-models",
  "futures",
- "migrations",
  "nanoid",
  "reqwest 0.12.9",
  "sea-orm",
  "sea-query",
- "slug",
  "supporting-service",
  "tracing",
 ]
diff --git a/apps/backend/src/main.rs b/apps/backend/src/main.rs
index bc524717ce..88696d464d 100644
--- a/apps/backend/src/main.rs
+++ b/apps/backend/src/main.rs
@@ -8,7 +8,7 @@ use std::{
 
 use anyhow::{bail, Result};
 use apalis::{
-    layers::{retry::RetryPolicy, WorkerBuilderExt},
+    layers::WorkerBuilderExt,
     prelude::{MemoryStorage, Monitor, WorkerBuilder, WorkerFactoryFn},
 };
 use apalis_cron::{CronStream, Schedule};
@@ -200,7 +200,6 @@ async fn main() -> Result<()> {
                 .catch_panic()
                 .enable_tracing()
                 .rate_limit(5, Duration::new(5, 0))
-                .retry(RetryPolicy::retries(3))
                 .data(app_services.clone())
                 .backend(mp_application_job_storage)
                 .build_fn(perform_mp_application_job),
diff --git a/apps/frontend/app/lib/state/collection.ts b/apps/frontend/app/lib/state/collection.ts
index 2d935db664..0c1621ff28 100644
--- a/apps/frontend/app/lib/state/collection.ts
+++ b/apps/frontend/app/lib/state/collection.ts
@@ -106,7 +106,9 @@ export const useBulkEditCollection = () => {
 									)
 								)
 									return clientGqlService
-										.request(PeopleListDocument, { input: { take } })
+										.request(PeopleListDocument, {
+											input: { search: { take } },
+										})
 										.then((r) =>
 											r.peopleList.items.map((p) => ({
 												entityId: p,
@@ -119,7 +121,9 @@ export const useBulkEditCollection = () => {
 									)
 								)
 									return clientGqlService
-										.request(MetadataGroupsListDocument, { input: { take } })
+										.request(MetadataGroupsListDocument, {
+											input: { search: { take } },
+										})
 										.then((r) =>
 											r.metadataGroupsList.items.map((p) => ({
 												entityId: p,
diff --git a/apps/frontend/app/routes/_dashboard._index.tsx b/apps/frontend/app/routes/_dashboard._index.tsx
index 4eca679b8d..eee0c80d55 100644
--- a/apps/frontend/app/routes/_dashboard._index.tsx
+++ b/apps/frontend/app/routes/_dashboard._index.tsx
@@ -116,9 +116,9 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
 	] = await Promise.all([
 		serverGqlService.authenticatedRequest(request, CollectionContentsDocument, {
 			input: {
-				collectionId: foundInProgressCollection.id,
-				take: takeInProgress,
+				search: { take: takeInProgress },
 				sort: { order: GraphqlSortOrder.Desc },
+				collectionId: foundInProgressCollection.id,
 			},
 		}),
 		getRecommendations(),
diff --git a/apps/frontend/app/routes/_dashboard.fitness.exercises.list.tsx b/apps/frontend/app/routes/_dashboard.fitness.exercises.list.tsx
index 1be430f64c..09e8411269 100644
--- a/apps/frontend/app/routes/_dashboard.fitness.exercises.list.tsx
+++ b/apps/frontend/app/routes/_dashboard.fitness.exercises.list.tsx
@@ -15,12 +15,17 @@ import {
 	Pagination,
 	Select,
 	SimpleGrid,
+	Skeleton,
 	Stack,
 	Text,
 	Title,
 	rem,
 } from "@mantine/core";
-import { useDisclosure, useListState } from "@mantine/hooks";
+import {
+	type UseListStateHandlers,
+	useDisclosure,
+	useListState,
+} from "@mantine/hooks";
 import type {
 	ActionFunctionArgs,
 	LoaderFunctionArgs,
@@ -51,6 +56,7 @@ import {
 	IconFilter,
 	IconPlus,
 } from "@tabler/icons-react";
+import { useQuery } from "@tanstack/react-query";
 import { produce } from "immer";
 import { $path } from "remix-routes";
 import { match } from "ts-pattern";
@@ -73,6 +79,8 @@ import {
 } from "~/lib/hooks";
 import {
 	addExerciseToWorkout,
+	getExerciseDetailsQuery,
+	getUserExerciseDetailsQuery,
 	useCurrentWorkout,
 	useMergingExercise,
 } from "~/lib/state/fitness";
@@ -173,20 +181,19 @@ const mergeExerciseSchema = z.object({
 	mergeInto: z.string(),
 });
 
+type SelectExercise = { name: string; lot: ExerciseLot };
+
 export default function Page() {
 	const loaderData = useLoaderData<typeof loader>();
 	const navigate = useNavigate();
-	const submit = useSubmit();
 	const coreDetails = useCoreDetails();
 	const userPreferences = useUserPreferences();
 	const [currentWorkout, setCurrentWorkout] = useCurrentWorkout();
 	const isFitnessActionActive = useIsFitnessActionActive();
 	const [mergingExercise, setMergingExercise] = useMergingExercise();
 	const [_, { setP }] = useAppSearchParam(loaderData.cookieName);
-	const [selectedExercises, setSelectedExercises] = useListState<{
-		name: string;
-		lot: ExerciseLot;
-	}>([]);
+	const [selectedExercises, setSelectedExercises] =
+		useListState<SelectExercise>([]);
 	const [
 		filtersModalOpened,
 		{ open: openFiltersModal, close: closeFiltersModal },
@@ -286,105 +293,16 @@ export default function Page() {
 								</Box>
 								<SimpleGrid cols={{ md: 2, lg: 3 }}>
 									{loaderData.exercisesList.items.map((exercise) => (
-										<Flex
-											key={exercise.id}
-											gap="lg"
-											align="center"
-											data-exercise-id={exercise.id}
-										>
-											{allowAddingExerciseToWorkout ? (
-												<Checkbox
-													onChange={(e) => {
-														if (e.currentTarget.checked)
-															setSelectedExercises.append({
-																name: exercise.id,
-																lot: exercise.lot,
-															});
-														else
-															setSelectedExercises.filter(
-																(item) => item.name !== exercise.id,
-															);
-													}}
-												/>
-											) : null}
-											<Indicator
-												size={16}
-												offset={8}
-												color="grape"
-												position="top-start"
-												disabled={!exercise.numTimesInteracted}
-												label={exercise.numTimesInteracted ?? ""}
-											>
-												<Avatar
-													size="lg"
-													radius="xl"
-													src={exercise.image}
-													imageProps={{ loading: "lazy" }}
-												/>
-											</Indicator>
-											<Link
-												style={{ all: "unset", cursor: "pointer" }}
-												to={getExerciseDetailsPath(exercise.id)}
-												onClick={(e) => {
-													if (allowAddingExerciseToWorkout) return;
-													if (mergingExercise) {
-														e.preventDefault();
-														openConfirmationModal(
-															"Are you sure you want to merge this exercise? This will replace this exercise in all workouts.",
-															() => {
-																const formData = new FormData();
-																formData.append("mergeFrom", mergingExercise);
-																formData.append("mergeInto", exercise.id);
-																setMergingExercise(null);
-																submit(formData, {
-																	method: "POST",
-																	action: withQuery(".", {
-																		intent: "mergeExercise",
-																	}),
-																});
-															},
-														);
-														return;
-													}
-													if (currentWorkout) {
-														e.preventDefault();
-														setCurrentWorkout(
-															produce(currentWorkout, (draft) => {
-																if (
-																	!isNumber(currentWorkout.replacingExerciseIdx)
-																)
-																	return;
-																draft.exercises[
-																	currentWorkout.replacingExerciseIdx
-																].exerciseId = exercise.id;
-																draft.replacingExerciseIdx = undefined;
-															}),
-														);
-														navigate(-1);
-														return;
-													}
-												}}
-											>
-												<Flex direction="column" justify="space-around">
-													<Text>{exercise.name}</Text>
-													<Flex>
-														{exercise.muscle ? (
-															<Text size="xs">
-																{startCase(snakeCase(exercise.muscle))}
-															</Text>
-														) : null}
-														{exercise.lastUpdatedOn ? (
-															<Text size="xs" c="dimmed">
-																{exercise.muscle ? "," : null}{" "}
-																{dayjsLib(exercise.lastUpdatedOn).format(
-																	"D MMM",
-																)}
-															</Text>
-														) : null}
-													</Flex>
-												</Flex>
-											</Link>
-										</Flex>
+										<ExerciseItemDisplay
+											key={exercise}
+											exerciseId={exercise}
+											mergingExercise={mergingExercise}
+											setMergingExercise={setMergingExercise}
+											setSelectedExercises={setSelectedExercises}
+											allowAddingExerciseToWorkout={
+												allowAddingExerciseToWorkout
+											}
+										/>
 									))}
 								</SimpleGrid>
 							</>
@@ -491,3 +409,119 @@ const FiltersModalForm = () => {
 		</MantineThemeProvider>
 	);
 };
+
+const ExerciseItemDisplay = (props: {
+	exerciseId: string;
+	mergingExercise: string | null;
+	allowAddingExerciseToWorkout: boolean | null;
+	setMergingExercise: (value: string | null) => void;
+	setSelectedExercises: UseListStateHandlers<SelectExercise>;
+}) => {
+	const [currentWorkout, setCurrentWorkout] = useCurrentWorkout();
+	const navigate = useNavigate();
+	const submit = useSubmit();
+	const { data: exercise } = useQuery(
+		getExerciseDetailsQuery(props.exerciseId),
+	);
+	const { data: userExerciseDetails } = useQuery(
+		getUserExerciseDetailsQuery(props.exerciseId),
+	);
+
+	const firstMuscle = exercise?.muscles?.at(0);
+	const numTimesInteracted =
+		userExerciseDetails?.details?.exerciseNumTimesInteracted;
+	const lastUpdatedOn = userExerciseDetails?.details?.lastUpdatedOn;
+
+	return exercise && userExerciseDetails ? (
+		<Flex gap="lg" align="center">
+			{props.allowAddingExerciseToWorkout ? (
+				<Checkbox
+					onChange={(e) => {
+						if (e.currentTarget.checked)
+							props.setSelectedExercises.append({
+								name: props.exerciseId,
+								lot: exercise.lot,
+							});
+						else
+							props.setSelectedExercises.filter(
+								(item) => item.name !== props.exerciseId,
+							);
+					}}
+				/>
+			) : null}
+			<Indicator
+				size={16}
+				offset={8}
+				color="grape"
+				position="top-start"
+				disabled={!numTimesInteracted}
+				label={numTimesInteracted ?? ""}
+			>
+				<Avatar
+					size="lg"
+					radius="xl"
+					imageProps={{ loading: "lazy" }}
+					src={exercise.attributes.images[0]}
+				/>
+			</Indicator>
+			<Link
+				style={{ all: "unset", cursor: "pointer" }}
+				to={getExerciseDetailsPath(props.exerciseId)}
+				onClick={(e) => {
+					if (props.allowAddingExerciseToWorkout) return;
+					if (props.mergingExercise) {
+						e.preventDefault();
+						openConfirmationModal(
+							"Are you sure you want to merge this exercise? This will replace this exercise in all workouts.",
+							() => {
+								const formData = new FormData();
+								if (props.mergingExercise)
+									formData.append("mergeFrom", props.mergingExercise);
+								formData.append("mergeInto", props.exerciseId);
+								props.setMergingExercise(null);
+								submit(formData, {
+									method: "POST",
+									action: withQuery(".", {
+										intent: "mergeExercise",
+									}),
+								});
+							},
+						);
+						return;
+					}
+					if (currentWorkout) {
+						e.preventDefault();
+						setCurrentWorkout(
+							produce(currentWorkout, (draft) => {
+								if (!isNumber(currentWorkout.replacingExerciseIdx)) return;
+								draft.exercises[
+									currentWorkout.replacingExerciseIdx
+								].exerciseId = props.exerciseId;
+								draft.replacingExerciseIdx = undefined;
+							}),
+						);
+						navigate(-1);
+						return;
+					}
+				}}
+			>
+				<Flex direction="column" justify="space-around">
+					<Text>{exercise.name}</Text>
+					<Flex>
+						{firstMuscle ? (
+							<Text size="xs">{startCase(snakeCase(firstMuscle))}</Text>
+						) : null}
+						{lastUpdatedOn ? (
+							<Text size="xs" c="dimmed">
+								{firstMuscle ? "," : null}{" "}
+								{dayjsLib(lastUpdatedOn).format("D MMM")}
+							</Text>
+						) : null}
+					</Flex>
+				</Flex>
+			</Link>
+		</Flex>
+	) : (
+		<Skeleton height={56} />
+	);
+};
diff --git a/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx b/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx
index c28eb28f51..0ab0deb05f 100644
--- a/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx
+++ b/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx
@@ -252,6 +252,13 @@ const editSeenItem = z.object({
 	providerWatchedOn: z.string().optional(),
 });
 
+const METADATA_LOTS_WITH_GRANULAR_UPDATES = [
+	MediaLot.Show,
+	MediaLot.Anime,
+	MediaLot.Manga,
+	MediaLot.Podcast,
+];
+
 export default function Page() {
 	const loaderData = useLoaderData<typeof loader>();
 	const userPreferences = useUserPreferences();
@@ -285,7 +292,7 @@ export default function Page() {
 		refreshUserMetadataDetails(loaderData.metadataId);
 	};
 
-	const PutOnHoldBtn = () => {
+	const PutOnHoldMenuItem = () => {
 		return (
 			<Form
 				action={withQuery($path("/actions"), {
@@ -301,7 +308,7 @@ export default function Page() {
 			</Form>
 		);
 	};
-	const DropBtn = () => {
+	const DropMenuItem = () => {
 		return (
 			<Form
 				action={withQuery($path("/actions"), {
@@ -320,8 +327,8 @@ export default function Page() {
 	const StateChangeButtons = () => {
 		return (
 			<>
-				<PutOnHoldBtn />
-				<DropBtn />
+				<PutOnHoldMenuItem />
+				<DropMenuItem />
 			</>
 		);
 	};
@@ -738,12 +745,12 @@ export default function Page() {
 																{`S${nextEntry.season}-E${nextEntry.episode}`}{" "}
 																as seen
 															</Menu.Item>
-															<PutOnHoldBtn />
+															<PutOnHoldMenuItem />
 														</>
 													) : null}
 													{loaderData.userMetadataDetails.history.length !==
 													0 ? (
-														<DropBtn />
+														<DropMenuItem />
 													) : (
 														<Menu.Item disabled>
 															No history. Update from the seasons tab.
@@ -751,42 +758,51 @@ export default function Page() {
 													)}
 												</>
 											) : null}
-											{loaderData.metadataDetails.lot === MediaLot.Anime &&
-											nextEntry ? (
+											{loaderData.metadataDetails.lot === MediaLot.Anime ? (
 												<>
 													<Menu.Label>Anime</Menu.Label>
 													<Menu.Item
 														onClick={() => {
 															setMetadataToUpdate({
 																metadataId: loaderData.metadataId,
-																animeEpisodeNumber: nextEntry.episode,
+																animeEpisodeNumber: nextEntry?.episode || 1,
 															});
 														}}
 													>
 														Mark EP-
-														{nextEntry.episode} as listened
+														{nextEntry?.episode || 1} as watched
 													</Menu.Item>
+													{nextEntry ? <PutOnHoldMenuItem /> : null}
+													{loaderData.userMetadataDetails.history.length !==
+													0 ? (
+														<DropMenuItem />
+													) : null}
 												</>
 											) : null}
-											{loaderData.metadataDetails.lot === MediaLot.Manga &&
-											nextEntry ? (
+											{loaderData.metadataDetails.lot === MediaLot.Manga ? (
 												<>
 													<Menu.Label>Manga</Menu.Label>
 													<Menu.Item
 														onClick={() => {
 															setMetadataToUpdate({
 																metadataId: loaderData.metadataId,
-																mangaChapterNumber: nextEntry.chapter,
-																mangaVolumeNumber: nextEntry.volume,
+																mangaVolumeNumber: nextEntry?.volume,
+																mangaChapterNumber: nextEntry?.chapter,
 															});
 														}}
 													>
 														Mark{" "}
-														{nextEntry.chapter
-															? `CH-${nextEntry.chapter}`
-															: `VOL-${nextEntry.volume}`}{" "}
+														{nextEntry &&
+															(nextEntry.chapter
+																? `CH-${nextEntry.chapter}`
+																: `VOL-${nextEntry.volume}`)}{" "}
 														as read
 													</Menu.Item>
+													{nextEntry ? <PutOnHoldMenuItem /> : null}
+													{loaderData.userMetadataDetails.history.length !==
+													0 ? (
+														<DropMenuItem />
+													) : null}
 												</>
 											) : null}
 											{loaderData.metadataDetails.lot === MediaLot.Podcast ? (
@@ -805,12 +821,12 @@ export default function Page() {
 																Mark EP-
 																{nextEntry.episode} as listened
 															</Menu.Item>
-															<PutOnHoldBtn />
+															<PutOnHoldMenuItem />
 														</>
 													) : null}
 													{loaderData.userMetadataDetails.history.length !==
 													0 ? (
-														<DropBtn />
+														<DropMenuItem />
 													) : (
 														<Menu.Item disabled>
 															No history. Update from the episodes tab.
@@ -830,9 +846,9 @@ export default function Page() {
 													>
 														Set progress
 													</Menu.Item>
-													{loaderData.metadataDetails.lot !== MediaLot.Show &&
-													loaderData.metadataDetails.lot !==
-														MediaLot.Podcast ? (
+													{!METADATA_LOTS_WITH_GRANULAR_UPDATES.includes(
+														loaderData.metadataDetails.lot,
+													) ? (
 														<StateChangeButtons />
 													) : null}
 													<Form
@@ -857,8 +873,9 @@ export default function Page() {
 														<Menu.Item type="submit">I finished it</Menu.Item>
 													</Form>
 												</>
-											) : loaderData.metadataDetails.lot !== MediaLot.Show &&
-												loaderData.metadataDetails.lot !== MediaLot.Podcast ? (
+											) : !METADATA_LOTS_WITH_GRANULAR_UPDATES.includes(
+													loaderData.metadataDetails.lot,
+												) ? (
 												<>
 													<Menu.Label>Not in progress</Menu.Label>
 													<Form
diff --git a/apps/frontend/app/routes/_dashboard.settings.imports-and-exports._index.tsx b/apps/frontend/app/routes/_dashboard.settings.imports-and-exports._index.tsx
index a8ed6474c4..652cfcde65 100644
--- a/apps/frontend/app/routes/_dashboard.settings.imports-and-exports._index.tsx
+++ b/apps/frontend/app/routes/_dashboard.settings.imports-and-exports._index.tsx
@@ -220,15 +220,11 @@ export default function Page() {
 									<Title order={2}>Import data</Title>
 									<Anchor
 										size="xs"
-										href={
-											deployImportSource
-												? withFragment(
-														`${coreDetails.docsLink}/importing.html`,
-														kebabCase(deployImportSource),
-													)
-												: ""
-										}
 										target="_blank"
+										href={withFragment(
+											`${coreDetails.docsLink}/importing.html`,
+											deployImportSource ? kebabCase(deployImportSource) : "",
+										)}
 									>
 										Docs
 									</Anchor>
@@ -425,63 +421,103 @@ export default function Page() {
 													data-import-report-id={report.id}
 												>
 													<Accordion.Control disabled={isInProgress}>
-														<Indicator
-															inline
-															size={12}
-															offset={-3}
-															processing={isInProgress}
-															color={
-																isInProgress
-																	? undefined
-																	: report.wasSuccess
-																		? "green"
-																		: "red"
-															}
-														>
-															{changeCase(report.source)}{" "}
-															<Text size="xs" span c="dimmed">
-																({dayjsLib(report.startedOn).fromNow()})
-															</Text>
-														</Indicator>
-														{isInProgress && report.progress ? (
-															<Progress
-																mt="xs"
-																animated
-																value={Number(report.progress)}
-															/>
-														) : null}
+														<Stack gap="xs">
+															<Box>
+																<Indicator
+																	inline
+																	size={12}
+																	offset={-3}
+																	processing={isInProgress}
+																	color={
+																		isInProgress
+																			? undefined
+																			: report.wasSuccess
+																				? "green"
+																				: "red"
+																	}
+																>
+																	{changeCase(report.source)}{" "}
+																	<Text size="xs" span c="dimmed">
+																		({dayjsLib(report.startedOn).fromNow()})
+																	</Text>
+																</Indicator>
+															</Box>
+															{isInProgress && report.progress ? (
+																<>
+																	<Box>
+																		<Text span fw="bold" mr={4}>
+																			Estimated to finish at:
+																		</Text>
+																		{dayjsLib(
+																			report.estimatedFinishTime,
+																		).format("lll")}
+																	</Box>
+																	<Progress
+																		animated
+																		value={Number(report.progress)}
+																	/>
+																</>
+															) : null}
+														</Stack>
 													</Accordion.Control>
 													<Accordion.Panel
 														styles={{ content: { paddingTop: 0 } }}
 													>
-														{report.details ? (
-															<Stack>
-																<Text>
-																	<Text span fw="bold" mr={4}>
-																		Total imported:
-																	</Text>
-																	{report.details.import.total},
-																	<Text span fw="bold" ml="md" mr={4}>
-																		Failed:
-																	</Text>
-																	{report.details.failedItems.length}
-																</Text>
-																{report.details.failedItems.length > 0 ? (
-																	<CodeHighlight
-																		mah={400}
-																		language="json"
-																		style={{ overflow: "scroll" }}
-																		code={JSON.stringify(
-																			report.details.failedItems,
-																			null,
-																			2,
-																		)}
-																	/>
-																) : null}
-															</Stack>
-														) : (
-															<Text>This import never finished</Text>
-														)}
+														<Stack>
+															<Box>
+																<Group>
+																	{report.details ? (
+																		<Box>
+																			<Text span fw="bold" mr={4}>
+																				Total imported:
+																			</Text>
+																			{report.details.import.total},
+																		</Box>
+																	) : null}
+																	<Box>
+																		<Text span fw="bold" mr={4}>
+																			Started at:
+																		</Text>
+																		{dayjsLib(report.startedOn).format("lll")}
+																	</Box>
+																</Group>
+																<Group>
+																	<Box>
+																		{report.finishedOn ? (
+																			<>
+																				<Text span fw="bold" mr={4}>
+																					Finished on:
+																				</Text>
+																				{dayjsLib(report.finishedOn).format(
+																					"lll",
+																				)}
+																			</>
+																		) : null}
+																	</Box>
+																	{report.details ? (
+																		<Box>
+																			<Text span fw="bold" mr={4}>
+																				Failed:
+																			</Text>
+																			{report.details.failedItems.length}
+																		</Box>
+																	) : null}
+																</Group>
+															</Box>
+															{report.details &&
+															report.details.failedItems.length > 0 ? (
+																<CodeHighlight
+																	mah={400}
+																	language="json"
+																	style={{ overflow: "scroll" }}
+																	code={JSON.stringify(
+																		report.details.failedItems,
+																		null,
+																		2,
+																	)}
+																/>
+															) : null}
+														</Stack>
 													</Accordion.Panel>
 												</Accordion.Item>
 											);
diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs
index e68e342c69..6a7f861484 100644
--- a/crates/migrations/src/lib.rs
+++ b/crates/migrations/src/lib.rs
@@ -60,6 +60,7 @@ mod m20241224_changes_for_issue_1139;
 mod m20241227_changes_for_issue_1157;
 mod m20241228_changes_for_issue_1130;
 mod m20241229_changes_for_issue_1155;
+mod m20250112_changes_for_issue_1174;
 
 pub use m20230410_create_metadata::Metadata as AliasedMetadata;
 pub use m20230413_create_person::Person as AliasedPerson;
@@ -141,6 +142,7 @@ impl MigratorTrait for Migrator {
             Box::new(m20241227_changes_for_issue_1157::Migration),
             Box::new(m20241228_changes_for_issue_1130::Migration),
             Box::new(m20241229_changes_for_issue_1155::Migration),
+            Box::new(m20250112_changes_for_issue_1174::Migration),
         ]
     }
 }
diff --git a/crates/migrations/src/m20230509_create_import_report.rs b/crates/migrations/src/m20230509_create_import_report.rs
index 30fe0f4d29..4c2ce9f336 100644
--- a/crates/migrations/src/m20230509_create_import_report.rs
+++ b/crates/migrations/src/m20230509_create_import_report.rs
@@ -17,6 +17,7 @@ pub enum ImportReport {
     FinishedOn,
     WasSuccess,
     SourceResult,
+    EstimatedFinishTime,
 }
 
 #[async_trait::async_trait]
@@ -45,6 +46,11 @@ impl MigrationTrait for Migration {
                     .col(ColumnDef::new(ImportReport::UserId).text().not_null())
                     .col(ColumnDef::new(ImportReport::Progress).decimal())
                     .col(ColumnDef::new(ImportReport::SourceResult).json_binary())
+                    .col(
+                        ColumnDef::new(ImportReport::EstimatedFinishTime)
+                            .timestamp_with_time_zone()
+                            .not_null(),
+                    )
                     .foreign_key(
                         ForeignKey::create()
                             .name("media_import_report_to_user_foreign_key")
diff --git a/crates/migrations/src/m20250112_changes_for_issue_1174.rs b/crates/migrations/src/m20250112_changes_for_issue_1174.rs
new file mode 100644
index 0000000000..7aa8081189
--- /dev/null
+++ b/crates/migrations/src/m20250112_changes_for_issue_1174.rs
@@ -0,0 +1,53 @@
+use sea_orm_migration::prelude::*;
+
+#[derive(DeriveMigrationName)]
+pub struct Migration;
+
+#[async_trait::async_trait]
+impl MigrationTrait for Migration {
+    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
+        let db = manager.get_connection();
+        if !manager
+            .has_column("import_report", "estimated_finish_time")
+            .await?
+        {
+            db.execute_unprepared(
+                r#"
+ALTER TABLE "import_report" ADD COLUMN "estimated_finish_time" timestamp with time zone;
+UPDATE "import_report" SET "estimated_finish_time" = ("started_on" + interval '1 hour');
+ALTER TABLE "import_report" ALTER COLUMN "estimated_finish_time" SET NOT NULL;
+                "#,
+            )
+            .await?;
+        }
+        for enum_name in ["ReviewConversion", "SeenHistoryConversion"] {
+            db.execute_unprepared(&format!(
+                r#"
+UPDATE import_report
+SET details = jsonb_set(
+    details,
+    '{{failed_items}}',
+    (
+        SELECT jsonb_agg(
+            CASE
+                WHEN (failed_item ->> 'step') = '{enum_name}' THEN
+                    jsonb_set(failed_item, '{{step}}', '"DatabaseCommit"')
+                ELSE
+                    failed_item
+            END
+        )
+        FROM jsonb_array_elements(details -> 'failed_items') AS failed_item
+    )
+)
+WHERE details -> 'failed_items' @> '[{{"step": "{enum_name}"}}]';
+        "#,
+            ))
+            .await?;
+        }
+        Ok(())
+    }
+
+    async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
+        Ok(())
+    }
+}
diff --git a/crates/models/common/src/lib.rs b/crates/models/common/src/lib.rs
index 4913c5a7e8..30edaf773f 100644
--- a/crates/models/common/src/lib.rs
+++ b/crates/models/common/src/lib.rs
@@ -157,6 +157,7 @@ pub struct NamedObject {
     Clone, Debug, Default, PartialEq, InputObject, FromJsonQueryResult, Eq, Serialize, Deserialize,
 )]
 pub struct SearchInput {
+    pub take: Option<u64>,
     pub page: Option<i32>,
     pub query: Option<String>,
 }
diff --git a/crates/models/database/src/import_report.rs b/crates/models/database/src/import_report.rs
index 6713568c26..7924fbd65d 100644
--- a/crates/models/database/src/import_report.rs
+++ b/crates/models/database/src/import_report.rs
@@ -20,6 +20,7 @@ pub struct Model {
     pub progress: Option<Decimal>,
     pub was_success: Option<bool>,
     pub finished_on: Option<DateTimeUtc>,
+    pub estimated_finish_time: DateTimeUtc,
     pub details: Option<ImportResultResponse>,
     #[graphql(skip)]
     pub source_result: Option<serde_json::Value>,
diff --git a/crates/models/database/src/seen.rs b/crates/models/database/src/seen.rs
index 970b24c1fe..b2f0d54236 100644
--- a/crates/models/database/src/seen.rs
+++ b/crates/models/database/src/seen.rs
@@ -101,9 +101,10 @@ impl ActiveModelBehavior for ActiveModel {
         let state = self.state.clone().unwrap();
         let progress = self.progress.clone().unwrap();
         let finished_on = self.finished_on.clone().unwrap();
+        let started_on = self.started_on.clone().unwrap();
         if progress == dec!(100) && state == SeenState::InProgress {
             self.state = ActiveValue::Set(SeenState::Completed);
-            if finished_on.is_none() {
+            if finished_on.is_none() && started_on.is_some() {
                 self.finished_on = ActiveValue::Set(Some(Utc::now().date_naive()));
             }
         }
diff --git a/crates/models/database/src/workout.rs b/crates/models/database/src/workout.rs
index a9f68ecc68..845f5fa753 100644
--- a/crates/models/database/src/workout.rs
+++ b/crates/models/database/src/workout.rs
@@ -8,8 +8,10 @@ use fitness_models::{WorkoutInformation, WorkoutSummary};
 use schematic::Schematic;
 use sea_orm::entity::prelude::*;
 use serde::{Deserialize, Serialize};
+use serde_with::skip_serializing_none;
 
 /// A workout that was completed by the user.
+#[skip_serializing_none]
 #[derive(
     Clone,
     Debug,
diff --git a/crates/models/database/src/workout_template.rs b/crates/models/database/src/workout_template.rs
index cf7e95ab58..1cdae8dca2 100644
--- a/crates/models/database/src/workout_template.rs
+++ b/crates/models/database/src/workout_template.rs
@@ -5,7 +5,9 @@ use fitness_models::{WorkoutInformation, WorkoutSummary};
 use schematic::Schematic;
 use sea_orm::entity::prelude::*;
 use serde::{Deserialize, Serialize};
+use serde_with::skip_serializing_none;
 
+#[skip_serializing_none]
 #[derive(
     Clone, Debug, PartialEq, DeriveEntityModel, Eq, SimpleObject, Serialize, Deserialize, Schematic,
 )]
diff --git a/crates/models/dependent/src/lib.rs b/crates/models/dependent/src/lib.rs
index 244bb91cfb..d95eee2910 100644
--- a/crates/models/dependent/src/lib.rs
+++ b/crates/models/dependent/src/lib.rs
@@ -34,7 +34,6 @@ use serde_with::skip_serializing_none;
 use strum::Display;
 
 #[derive(PartialEq, Eq, Default, Serialize, Deserialize, Debug, SimpleObject, Clone)]
-#[graphql(concrete(name = "ExerciseListResults", params(fitness_models::ExerciseListItem)))]
 #[graphql(concrete(
     name = "MediaCollectionContentsResults",
     params(media_models::EntityWithLot)
diff --git a/crates/models/fitness/src/lib.rs b/crates/models/fitness/src/lib.rs
index 620b58a08a..c0099f98e5 100644
--- a/crates/models/fitness/src/lib.rs
+++ b/crates/models/fitness/src/lib.rs
@@ -10,7 +10,7 @@ use enum_models::{
 };
 use rust_decimal::Decimal;
 use schematic::{ConfigEnum, Schematic};
-use sea_orm::{prelude::DateTimeUtc, FromJsonQueryResult, FromQueryResult};
+use sea_orm::{prelude::DateTimeUtc, FromJsonQueryResult};
 use serde::{Deserialize, Serialize};
 use serde_with::skip_serializing_none;
 
@@ -121,21 +121,6 @@ pub struct UserMeasurementStats {
     pub custom: Option<HashMap<String, Decimal>>,
 }
 
-#[derive(Clone, Debug, Deserialize, SimpleObject, FromQueryResult)]
-pub struct ExerciseListItem {
-    pub id: String,
-    pub name: String,
-    pub lot: ExerciseLot,
-    #[graphql(skip)]
-    pub attributes: ExerciseAttributes,
-    pub num_times_interacted: Option<i32>,
-    pub last_updated_on: Option<DateTimeUtc>,
-    pub muscle: Option<ExerciseMuscle>,
-    pub image: Option<String>,
-    #[graphql(skip)]
-    pub muscles: Vec<ExerciseMuscle>,
-}
-
 /// The totals of a workout and the different bests achieved.
 #[derive(
     Eq,
@@ -601,6 +586,7 @@ pub struct WorkoutFocusedSummary {
     pub equipments: Vec<WorkoutEquipmentFocusedSummary>,
 }
 
+#[skip_serializing_none]
 #[derive(
     Clone,
     Debug,
@@ -685,11 +671,11 @@ pub enum ExerciseSortBy {
     TimesPerformed,
 }
 
-#[derive(Debug, Serialize, Deserialize, InputObject, Clone)]
+#[derive(Debug, Serialize, Deserialize, InputObject, Clone, Default)]
 pub struct ExercisesListInput {
     pub search: SearchInput,
-    pub filter: Option<ExerciseListFilter>,
     pub sort_by: Option<ExerciseSortBy>,
+    pub filter: Option<ExerciseListFilter>,
 }
 
 #[derive(Debug, InputObject)]
diff --git a/crates/models/importer/src/lib.rs b/crates/models/importer/src/lib.rs
index fa3c05bb33..dd6e40699c 100644
--- a/crates/models/importer/src/lib.rs
+++ b/crates/models/importer/src/lib.rs
@@ -6,17 +6,15 @@ use serde::{Deserialize, Serialize};
 /// The various steps in which media importing can fail
 #[derive(Debug, Default, Enum, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
 pub enum ImportFailStep {
-    /// Failed to get details from the source itself (for eg: MediaTracker, Goodreads etc.)
+    /// Failed to get details from the source (eg: MediaTracker, Goodreads etc.)
     #[default]
     ItemDetailsFromSource,
-    /// Failed to get metadata from the provider (for eg: Openlibrary, IGDB etc.)
-    MediaDetailsFromProvider,
     /// Failed to transform the data into the required format
     InputTransformation,
-    /// Failed to save a seen history item
-    SeenHistoryConversion,
-    /// Failed to save a review/rating item
-    ReviewConversion,
+    /// Failed to get metadata from the provider (eg: Openlibrary, IGDB etc.)
+    MediaDetailsFromProvider,
+    /// Failed to save an entity/review/progress item
+    DatabaseCommit,
 }
 
 #[derive(
diff --git a/crates/models/media/src/lib.rs b/crates/models/media/src/lib.rs
index fe7c27f4ed..1bfdb00504 100644
--- a/crates/models/media/src/lib.rs
+++ b/crates/models/media/src/lib.rs
@@ -535,8 +535,6 @@ pub struct ImportOrExportItemRating {
 #[derive(Debug, Serialize, Deserialize, Clone, Schematic, Default)]
 #[serde(rename_all = "snake_case")]
 pub struct ImportOrExportMetadataItem {
-    #[serde(skip)]
-    pub id: String,
     /// The type of media.
     pub lot: MediaLot,
     /// An string to help identify it in the original source.
@@ -558,8 +556,6 @@ pub struct ImportOrExportMetadataItem {
 #[derive(Debug, Serialize, Deserialize, Clone, Schematic, Default)]
 #[serde(rename_all = "snake_case")]
 pub struct ImportOrExportMetadataGroupItem {
-    #[serde(skip)]
-    pub id: String,
     /// Name of the group.
     pub title: String,
     /// The type of media.
@@ -579,8 +575,6 @@ pub struct ImportOrExportMetadataGroupItem {
 #[derive(Debug, Serialize, Deserialize, Clone, Schematic, Default)]
 #[serde(rename_all = "snake_case")]
 pub struct ImportOrExportPersonItem {
-    #[serde(skip)]
-    pub id: String,
     /// The name of the creator.
     pub name: String,
     /// The provider identifier.
@@ -1150,7 +1144,6 @@ pub struct CollectionContentsFilter {
 
 #[derive(Debug, InputObject)]
 pub struct CollectionContentsInput {
-    pub take: Option<u64>,
     pub collection_id: String,
     pub search: Option<SearchInput>,
     pub filter: Option<CollectionContentsFilter>,
@@ -1291,8 +1284,9 @@ pub struct SortInput<T: InputType + Default> {
     pub by: T,
 }
 
-#[derive(Debug, Serialize, Deserialize, Enum, Clone, Copy, Eq, PartialEq)]
+#[derive(Debug, Serialize, Deserialize, Enum, Clone, Copy, Eq, PartialEq, Default)]
 pub enum MediaGeneralFilter {
+    #[default]
     All,
     Rated,
     Unrated,
@@ -1301,34 +1295,31 @@ pub enum MediaGeneralFilter {
     Unfinished,
 }
 
-#[derive(Debug, Serialize, Deserialize, InputObject, Clone)]
+#[derive(Debug, Serialize, Deserialize, InputObject, Clone, Default)]
 pub struct MediaFilter {
-    pub general: Option<MediaGeneralFilter>,
     pub collections: Option<Vec<String>>,
+    pub general: Option<MediaGeneralFilter>,
 }
 
-#[derive(Debug, Serialize, Deserialize, InputObject, Clone)]
+#[derive(Debug, Serialize, Deserialize, InputObject, Clone, Default)]
 pub struct MetadataListInput {
-    pub take: Option<u64>,
     pub lot: Option<MediaLot>,
     pub filter: Option<MediaFilter>,
     pub search: Option<SearchInput>,
-    pub sort: Option<SortInput<MediaSortBy>>,
     pub invert_collection: Option<bool>,
+    pub sort: Option<SortInput<MediaSortBy>>,
 }
 
-#[derive(Debug, Serialize, Deserialize, InputObject, Clone)]
+#[derive(Debug, Serialize, Deserialize, InputObject, Clone, Default)]
 pub struct PeopleListInput {
-    pub take: Option<u64>,
     pub search: Option<SearchInput>,
     pub filter: Option<MediaFilter>,
     pub invert_collection: Option<bool>,
     pub sort: Option<SortInput<PersonAndMetadataGroupsSortBy>>,
 }
 
-#[derive(Debug, Serialize, Deserialize, InputObject, Clone)]
+#[derive(Debug, Serialize, Deserialize, InputObject, Clone, Default)]
 pub struct MetadataGroupsListInput {
-    pub take: Option<u64>,
     pub search: Option<SearchInput>,
     pub filter: Option<MediaFilter>,
     pub invert_collection: Option<bool>,
diff --git a/crates/resolvers/fitness/src/lib.rs b/crates/resolvers/fitness/src/lib.rs
index 8fd86e2058..639cdee705 100644
--- a/crates/resolvers/fitness/src/lib.rs
+++ b/crates/resolvers/fitness/src/lib.rs
@@ -8,8 +8,8 @@ use dependent_models::{
     UserWorkoutTemplateDetails,
 };
 use fitness_models::{
-    ExerciseListItem, ExercisesListInput, UpdateUserExerciseSettings,
-    UpdateUserWorkoutAttributesInput, UserMeasurementsListInput, UserWorkoutInput,
+    ExercisesListInput, UpdateUserExerciseSettings, UpdateUserWorkoutAttributesInput,
+    UserMeasurementsListInput, UserWorkoutInput,
 };
 use fitness_service::FitnessService;
 use sea_orm::prelude::DateTimeUtc;
@@ -51,7 +51,7 @@ impl FitnessQuery {
         &self,
         gql_ctx: &Context<'_>,
         input: ExercisesListInput,
-    ) -> Result<SearchResults<ExerciseListItem>> {
+    ) -> Result<SearchResults<String>> {
         let service = gql_ctx.data_unchecked::<Arc<FitnessService>>();
         let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.exercises_list(user_id, input).await
diff --git a/crates/resolvers/router/src/lib.rs b/crates/resolvers/router/src/lib.rs
index 1a59eff0f3..dbe5d2bc19 100644
--- a/crates/resolvers/router/src/lib.rs
+++ b/crates/resolvers/router/src/lib.rs
@@ -56,7 +56,7 @@ pub async fn integration_webhook(
         .process_integration_webhook(integration_slug, payload)
         .await
         .map_err(|e| {
-            ryot_log!(error, "{:?}", e);
+            ryot_log!(debug, "{:?}", e);
             StatusCode::UNPROCESSABLE_ENTITY
         })?;
     Ok((StatusCode::OK, response))
diff --git a/crates/services/collection/src/lib.rs b/crates/services/collection/src/lib.rs
index 1316dcf3d7..828e89141e 100644
--- a/crates/services/collection/src/lib.rs
+++ b/crates/services/collection/src/lib.rs
@@ -152,6 +152,11 @@ impl CollectionService {
         &self,
         input: CollectionContentsInput,
     ) -> Result<CollectionContents> {
+        let take = input
+            .search
+            .clone()
+            .and_then(|s| s.take)
+            .unwrap_or(PAGE_SIZE as u64);
         let search = input.search.unwrap_or_default();
         let sort = input.sort.unwrap_or_default();
         let filter = input.filter.unwrap_or_default();
@@ -163,8 +168,6 @@ impl CollectionService {
         let Some(collection) = maybe_collection else {
             return Err(Error::new("Collection not found".to_owned()));
         };
-
-        let take = input.take.unwrap_or_else(|| PAGE_SIZE.try_into().unwrap());
         let results = if take != 0 {
             let paginator = CollectionToEntity::find()
                 .left_join(Metadata)
diff --git a/crates/services/exporter/Cargo.toml b/crates/services/exporter/Cargo.toml
index 9a07c81831..40306a6acf 100644
--- a/crates/services/exporter/Cargo.toml
+++ b/crates/services/exporter/Cargo.toml
@@ -12,6 +12,7 @@ common-utils = { path = "../../utils/common" }
 database-models = { path = "../../models/database" }
 database-utils = { path = "../../utils/database" }
 dependent-models = { path = "../../models/dependent" }
+dependent-utils = { path = "../../utils/dependent" }
 enum-models = { path = "../../models/enum" }
 itertools = { workspace = true }
 fitness-models = { path = "../../models/fitness" }
@@ -24,3 +25,7 @@ struson = { workspace = true }
 supporting-service = { path = "../supporting" }
 tokio = { workspace = true }
 tokio-util = { workspace = true }
+tracing = { workspace = true }
+
+[package.metadata.cargo-machete]
+ignored = ["tracing"]
diff --git a/crates/services/exporter/src/lib.rs b/crates/services/exporter/src/lib.rs
index 5a9e1c5e2f..0f3425e3c5 100644
--- a/crates/services/exporter/src/lib.rs
+++ b/crates/services/exporter/src/lib.rs
@@ -3,27 +3,29 @@ use std::{collections::HashMap, fs::File as StdFile, path::PathBuf, sync::Arc};
 use async_graphql::{Error, Result};
 use background_models::{ApplicationJob, MpApplicationJob};
 use chrono::{DateTime, Utc};
-use common_models::ExportJob;
-use common_utils::TEMP_DIR;
+use common_models::{ExportJob, SearchInput};
+use common_utils::{ryot_log, TEMP_DIR};
 use database_models::{
-    exercise,
-    prelude::{
-        Exercise, Metadata, MetadataGroup, Person, Seen, UserToEntity, Workout, WorkoutTemplate,
-    },
-    seen, user_to_entity, workout, workout_template,
+    prelude::{Exercise, Metadata, MetadataGroup, Person, Seen},
+    seen,
 };
 use database_utils::{
     entity_in_collections, item_reviews, user_measurements_list, user_workout_details,
     user_workout_template_details,
 };
 use dependent_models::{ImportOrExportWorkoutItem, ImportOrExportWorkoutTemplateItem};
+use dependent_utils::{
+    exercises_list, metadata_groups_list, metadata_list, people_list, user_workout_templates_list,
+    user_workouts_list,
+};
 use enum_models::EntityLot;
-use fitness_models::UserMeasurementsListInput;
+use fitness_models::{ExercisesListInput, UserMeasurementsListInput};
 use itertools::Itertools;
 use media_models::{
     ImportOrExportExerciseItem, ImportOrExportItemRating, ImportOrExportItemReview,
     ImportOrExportMetadataGroupItem, ImportOrExportMetadataItem, ImportOrExportMetadataItemSeen,
-    ImportOrExportPersonItem, ReviewItem,
+    ImportOrExportPersonItem, MetadataGroupsListInput, MetadataListInput, PeopleListInput,
+    ReviewItem,
 };
 use nanoid::nanoid;
 use reqwest::{
@@ -32,7 +34,6 @@ use reqwest::{
 };
 use sea_orm::{
     strum::Display, ColumnTrait, EntityTrait, EnumIter, Iterable, ModelTrait, QueryFilter,
-    QueryOrder, QuerySelect,
 };
 use struson::writer::{JsonStreamWriter, JsonWriter};
 use supporting_service::SupportingService;
@@ -42,12 +43,12 @@ use tokio_util::codec::{BytesCodec, FramedRead};
 #[derive(Eq, PartialEq, Copy, Display, Clone, Debug, EnumIter)]
 #[strum(serialize_all = "snake_case")]
 enum ExportItem {
-    Media,
     People,
     Workouts,
+    Metadata,
     Exercises,
-    MediaGroups,
     Measurements,
+    MetadataGroups,
     WorkoutTemplates,
 }
 
@@ -112,14 +113,17 @@ impl ExporterService {
         let mut writer = JsonStreamWriter::new(file);
         writer.begin_object().unwrap();
         for export in ExportItem::iter() {
+            ryot_log!(debug, "Exporting {export}");
             writer.name(&export.to_string())?;
             writer.begin_array().unwrap();
             match export {
-                ExportItem::Media => self.export_media(&user_id, &mut writer).await?,
+                ExportItem::Metadata => self.export_media(&user_id, &mut writer).await?,
                 ExportItem::People => self.export_people(&user_id, &mut writer).await?,
                 ExportItem::Workouts => self.export_workouts(&user_id, &mut writer).await?,
                 ExportItem::Exercises => self.export_exercises(&user_id, &mut writer).await?,
-                ExportItem::MediaGroups => self.export_media_group(&user_id, &mut writer).await?,
+                ExportItem::MetadataGroups => {
+                    self.export_media_group(&user_id, &mut writer).await?
+                }
                 ExportItem::Measurements => self.export_measurements(&user_id, &mut writer).await?,
                 ExportItem::WorkoutTemplates => {
                     self.export_workout_templates(&user_id, &mut writer).await?
@@ -129,6 +133,7 @@ impl ExporterService {
         }
         writer.end_object().unwrap();
         writer.finish_document().unwrap();
+        ryot_log!(debug, "Exporting completed");
         let ended_at = Utc::now();
         let (_key, url) = self
             .0
@@ -172,73 +177,88 @@ impl ExporterService {
         user_id: &String,
         writer: &mut JsonStreamWriter<StdFile>,
     ) -> Result<()> {
-        let related_metadata = UserToEntity::find()
-            .filter(user_to_entity::Column::UserId.eq(user_id))
-            .filter(user_to_entity::Column::MetadataId.is_not_null())
-            .all(&self.0.db)
-            .await
-            .unwrap();
-        for rm in related_metadata.iter() {
-            let m = rm
-                .find_related(Metadata)
-                .one(&self.0.db)
-                .await
-                .unwrap()
-                .unwrap();
-            let seen_history = m
-                .find_related(Seen)
-                .filter(seen::Column::UserId.eq(user_id))
-                .all(&self.0.db)
-                .await
-                .unwrap();
-            let seen_history = seen_history
-                .into_iter()
-                .map(|s| {
-                    let (show_season_number, show_episode_number) = match s.show_extra_information {
-                        Some(d) => (Some(d.season), Some(d.episode)),
-                        None => (None, None),
-                    };
-                    let podcast_episode_number = s.podcast_extra_information.map(|d| d.episode);
-                    let anime_episode_number = s.anime_extra_information.and_then(|d| d.episode);
-                    let manga_chapter_number =
-                        s.manga_extra_information.clone().and_then(|d| d.chapter);
-                    let manga_volume_number = s.manga_extra_information.and_then(|d| d.volume);
-                    ImportOrExportMetadataItemSeen {
-                        progress: Some(s.progress),
-                        started_on: s.started_on,
-                        ended_on: s.finished_on,
-                        provider_watched_on: s.provider_watched_on,
-                        show_season_number,
-                        show_episode_number,
-                        podcast_episode_number,
-                        anime_episode_number,
-                        manga_chapter_number,
-                        manga_volume_number,
-                    }
-                })
-                .collect();
-            let reviews = item_reviews(user_id, &m.id, EntityLot::Metadata, false, &self.0)
-                .await?
-                .into_iter()
-                .map(|r| self.get_review_export_item(r))
-                .collect();
-            let collections =
-                entity_in_collections(&self.0.db, user_id, &m.id, EntityLot::Metadata)
+        let mut current_page = 1;
+        loop {
+            let related_metadata = metadata_list(
+                user_id,
+                MetadataListInput {
+                    search: Some(SearchInput {
+                        take: Some(1000),
+                        page: Some(current_page),
+                        ..Default::default()
+                    }),
+                    ..Default::default()
+                },
+                &self.0,
+            )
+            .await?;
+            ryot_log!(debug, "Exporting metadata list page: {current_page}");
+            for rm in related_metadata.items.iter() {
+                let m = Metadata::find_by_id(rm)
+                    .one(&self.0.db)
                     .await?
+                    .ok_or_else(|| Error::new("Metadata with the given ID does not exist"))?;
+                let seen_history = m
+                    .find_related(Seen)
+                    .filter(seen::Column::UserId.eq(user_id))
+                    .all(&self.0.db)
+                    .await
+                    .unwrap();
+                let seen_history = seen_history
                     .into_iter()
-                    .map(|c| c.name)
+                    .map(|s| {
+                        let (show_season_number, show_episode_number) =
+                            match s.show_extra_information {
+                                Some(d) => (Some(d.season), Some(d.episode)),
+                                None => (None, None),
+                            };
+                        let podcast_episode_number = s.podcast_extra_information.map(|d| d.episode);
+                        let anime_episode_number =
+                            s.anime_extra_information.and_then(|d| d.episode);
+                        let manga_chapter_number =
+                            s.manga_extra_information.clone().and_then(|d| d.chapter);
+                        let manga_volume_number = s.manga_extra_information.and_then(|d| d.volume);
+                        ImportOrExportMetadataItemSeen {
+                            progress: Some(s.progress),
+                            started_on: s.started_on,
+                            ended_on: s.finished_on,
+                            provider_watched_on: s.provider_watched_on,
+                            show_season_number,
+                            show_episode_number,
+                            podcast_episode_number,
+                            anime_episode_number,
+                            manga_chapter_number,
+                            manga_volume_number,
+                        }
+                    })
                     .collect();
-            let exp = ImportOrExportMetadataItem {
-                reviews,
-                lot: m.lot,
-                collections,
-                seen_history,
-                source: m.source,
-                source_id: m.title,
-                identifier: m.identifier.clone(),
-                ..Default::default()
-            };
-            writer.serialize_value(&exp).unwrap();
+                let reviews = item_reviews(user_id, &m.id, EntityLot::Metadata, false, &self.0)
+                    .await?
+                    .into_iter()
+                    .map(|r| self.get_review_export_item(r))
+                    .collect();
+                let collections =
+                    entity_in_collections(&self.0.db, user_id, &m.id, EntityLot::Metadata)
+                        .await?
+                        .into_iter()
+                        .map(|c| c.name)
+                        .collect();
+                let exp = ImportOrExportMetadataItem {
+                    reviews,
+                    lot: m.lot,
+                    collections,
+                    seen_history,
+                    source: m.source,
+                    source_id: m.title,
+                    identifier: m.identifier.clone(),
+                };
+                writer.serialize_value(&exp).unwrap();
+            }
+            if let Some(next_page) = related_metadata.details.next_page {
+                current_page = next_page;
+            } else {
+                break;
+            }
         }
         Ok(())
     }
@@ -248,40 +268,54 @@ impl ExporterService {
         user_id: &String,
         writer: &mut JsonStreamWriter<StdFile>,
     ) -> Result<()> {
-        let related_metadata = UserToEntity::find()
-            .filter(user_to_entity::Column::UserId.eq(user_id))
-            .filter(user_to_entity::Column::MetadataGroupId.is_not_null())
-            .all(&self.0.db)
-            .await
-            .unwrap();
-        for rm in related_metadata.iter() {
-            let m = rm
-                .find_related(MetadataGroup)
-                .one(&self.0.db)
-                .await
-                .unwrap()
-                .unwrap();
-            let reviews = item_reviews(user_id, &m.id, EntityLot::MetadataGroup, false, &self.0)
-                .await?
-                .into_iter()
-                .map(|r| self.get_review_export_item(r))
-                .collect();
-            let collections =
-                entity_in_collections(&self.0.db, user_id, &m.id, EntityLot::MetadataGroup)
+        let mut current_page = 1;
+        loop {
+            let related_metadata = metadata_groups_list(
+                user_id,
+                &self.0,
+                MetadataGroupsListInput {
+                    search: Some(SearchInput {
+                        take: Some(1000),
+                        page: Some(current_page),
+                        ..Default::default()
+                    }),
+                    ..Default::default()
+                },
+            )
+            .await?;
+            ryot_log!(debug, "Exporting metadata groups list page: {current_page}");
+            for rm in related_metadata.items.iter() {
+                let m = MetadataGroup::find_by_id(rm)
+                    .one(&self.0.db)
                     .await?
-                    .into_iter()
-                    .map(|c| c.name)
-                    .collect();
-            let exp = ImportOrExportMetadataGroupItem {
-                reviews,
-                lot: m.lot,
-                collections,
-                title: m.title,
-                source: m.source,
-                identifier: m.identifier.clone(),
-                ..Default::default()
-            };
-            writer.serialize_value(&exp).unwrap();
+                    .ok_or_else(|| Error::new("Metadata group with the given ID does not exist"))?;
+                let reviews =
+                    item_reviews(user_id, &m.id, EntityLot::MetadataGroup, false, &self.0)
+                        .await?
+                        .into_iter()
+                        .map(|r| self.get_review_export_item(r))
+                        .collect();
+                let collections =
+                    entity_in_collections(&self.0.db, user_id, &m.id, EntityLot::MetadataGroup)
+                        .await?
+                        .into_iter()
+                        .map(|c| c.name)
+                        .collect();
+                let exp = ImportOrExportMetadataGroupItem {
+                    reviews,
+                    lot: m.lot,
+                    collections,
+                    title: m.title,
+                    source: m.source,
+                    identifier: m.identifier.clone(),
+                };
+                writer.serialize_value(&exp).unwrap();
+            }
+            if let Some(next_page) = related_metadata.details.next_page {
+                current_page = next_page;
+            } else {
+                break;
+            }
         }
         Ok(())
     }
@@ -291,39 +325,53 @@ impl ExporterService {
         user_id: &String,
         writer: &mut JsonStreamWriter<StdFile>,
     ) -> Result<()> {
-        let related_people = UserToEntity::find()
-            .filter(user_to_entity::Column::UserId.eq(user_id))
-            .filter(user_to_entity::Column::PersonId.is_not_null())
-            .all(&self.0.db)
-            .await
-            .unwrap();
-        for rm in related_people.iter() {
-            let p = rm
-                .find_related(Person)
-                .one(&self.0.db)
-                .await
-                .unwrap()
-                .unwrap();
-            let reviews = item_reviews(user_id, &p.id, EntityLot::Person, false, &self.0)
-                .await?
-                .into_iter()
-                .map(|r| self.get_review_export_item(r))
-                .collect();
-            let collections = entity_in_collections(&self.0.db, user_id, &p.id, EntityLot::Person)
-                .await?
-                .into_iter()
-                .map(|c| c.name)
-                .collect();
-            let exp = ImportOrExportPersonItem {
-                reviews,
-                collections,
-                name: p.name,
-                source: p.source,
-                identifier: p.identifier,
-                source_specifics: p.source_specifics,
-                ..Default::default()
-            };
-            writer.serialize_value(&exp).unwrap();
+        let mut current_page = 1;
+        loop {
+            let related_people = people_list(
+                user_id,
+                PeopleListInput {
+                    search: Some(SearchInput {
+                        take: Some(1000),
+                        page: Some(current_page),
+                        ..Default::default()
+                    }),
+                    ..Default::default()
+                },
+                &self.0,
+            )
+            .await?;
+            ryot_log!(debug, "Exporting people list page: {current_page}");
+            for rm in related_people.items.iter() {
+                let p = Person::find_by_id(rm)
+                    .one(&self.0.db)
+                    .await?
+                    .ok_or_else(|| Error::new("Person with the given ID does not exist"))?;
+                let reviews = item_reviews(user_id, &p.id, EntityLot::Person, false, &self.0)
+                    .await?
+                    .into_iter()
+                    .map(|r| self.get_review_export_item(r))
+                    .collect();
+                let collections =
+                    entity_in_collections(&self.0.db, user_id, &p.id, EntityLot::Person)
+                        .await?
+                        .into_iter()
+                        .map(|c| c.name)
+                        .collect();
+                let exp = ImportOrExportPersonItem {
+                    reviews,
+                    collections,
+                    name: p.name,
+                    source: p.source,
+                    identifier: p.identifier,
+                    source_specifics: p.source_specifics,
+                };
+                writer.serialize_value(&exp).unwrap();
+            }
+            if let Some(next_page) = related_people.details.next_page {
+                current_page = next_page;
+            } else {
+                break;
+            }
         }
         Ok(())
     }
@@ -333,21 +381,32 @@ impl ExporterService {
         user_id: &String,
         writer: &mut JsonStreamWriter<StdFile>,
     ) -> Result<()> {
-        let workout_ids = Workout::find()
-            .select_only()
-            .column(workout::Column::Id)
-            .filter(workout::Column::UserId.eq(user_id))
-            .order_by_desc(workout::Column::EndTime)
-            .into_tuple::<String>()
-            .all(&self.0.db)
+        let mut current_page = 1;
+        loop {
+            let workout_ids = user_workouts_list(
+                user_id,
+                SearchInput {
+                    take: Some(1000),
+                    page: Some(current_page),
+                    ..Default::default()
+                },
+                &self.0,
+            )
             .await?;
-        for workout_id in workout_ids {
-            let details = user_workout_details(user_id, workout_id, &self.0).await?;
-            let exp = ImportOrExportWorkoutItem {
-                details: details.details,
-                collections: details.collections.into_iter().map(|c| c.name).collect(),
-            };
-            writer.serialize_value(&exp).unwrap();
+            ryot_log!(debug, "Exporting workouts list page: {current_page}");
+            for workout_id in workout_ids.items {
+                let details = user_workout_details(user_id, workout_id, &self.0).await?;
+                let exp = ImportOrExportWorkoutItem {
+                    details: details.details,
+                    collections: details.collections.into_iter().map(|c| c.name).collect(),
+                };
+                writer.serialize_value(&exp).unwrap();
+            }
+            if let Some(next_page) = workout_ids.details.next_page {
+                current_page = next_page;
+            } else {
+                break;
+            }
         }
         Ok(())
     }
@@ -371,39 +430,54 @@ impl ExporterService {
         user_id: &String,
         writer: &mut JsonStreamWriter<StdFile>,
     ) -> Result<()> {
-        let exercises = UserToEntity::find()
-            .select_only()
-            .column(exercise::Column::Id)
-            .column(exercise::Column::Name)
-            .filter(user_to_entity::Column::UserId.eq(user_id))
-            .filter(user_to_entity::Column::ExerciseId.is_not_null())
-            .left_join(Exercise)
-            .into_tuple::<(String, String)>()
-            .all(&self.0.db)
-            .await
-            .unwrap();
-        for (exercise_id, exercise_name) in exercises {
-            let reviews = item_reviews(user_id, &exercise_id, EntityLot::Exercise, false, &self.0)
-                .await?
-                .into_iter()
-                .map(|r| self.get_review_export_item(r))
-                .collect_vec();
-            let collections =
-                entity_in_collections(&self.0.db, user_id, &exercise_id, EntityLot::Exercise)
+        let mut current_page = 1;
+        loop {
+            let exercises = exercises_list(
+                user_id,
+                ExercisesListInput {
+                    search: SearchInput {
+                        take: Some(1000),
+                        page: Some(current_page),
+                        ..Default::default()
+                    },
+                    ..Default::default()
+                },
+                &self.0,
+            )
+            .await?;
+            for exercise_id in exercises.items {
+                let reviews =
+                    item_reviews(user_id, &exercise_id, EntityLot::Exercise, false, &self.0)
+                        .await?
+                        .into_iter()
+                        .map(|r| self.get_review_export_item(r))
+                        .collect_vec();
+                let collections =
+                    entity_in_collections(&self.0.db, user_id, &exercise_id, EntityLot::Exercise)
+                        .await?
+                        .into_iter()
+                        .map(|c| c.name)
+                        .collect_vec();
+                if reviews.is_empty() && collections.is_empty() {
+                    continue;
+                }
+                let exercise = Exercise::find_by_id(exercise_id.clone())
+                    .one(&self.0.db)
                     .await?
-                    .into_iter()
-                    .map(|c| c.name)
-                    .collect_vec();
-            if reviews.is_empty() && collections.is_empty() {
-                continue;
+                    .ok_or_else(|| Error::new("Exercise with the given ID does not exist"))?;
+                let exp = ImportOrExportExerciseItem {
+                    reviews,
+                    collections,
+                    id: exercise_id,
+                    name: exercise.name,
+                };
+                writer.serialize_value(&exp).unwrap();
+            }
+            if let Some(next_page) = exercises.details.next_page {
+                current_page = next_page;
+            } else {
+                break;
             }
-            let exp = ImportOrExportExerciseItem {
-                reviews,
-                collections,
-                id: exercise_id,
-                name: exercise_name,
-            };
-            writer.serialize_value(&exp).unwrap();
         }
         Ok(())
     }
@@ -413,22 +487,36 @@ impl ExporterService {
         user_id: &String,
         writer: &mut JsonStreamWriter<StdFile>,
     ) -> Result<()> {
-        let workout_template_ids = WorkoutTemplate::find()
-            .select_only()
-            .column(workout_template::Column::Id)
-            .filter(workout_template::Column::UserId.eq(user_id))
-            .order_by_desc(workout_template::Column::CreatedOn)
-            .into_tuple::<String>()
-            .all(&self.0.db)
+        let mut current_page = 1;
+        loop {
+            let workout_template_ids = user_workout_templates_list(
+                user_id,
+                SearchInput {
+                    take: Some(1000),
+                    page: Some(current_page),
+                    ..Default::default()
+                },
+                &self.0,
+            )
             .await?;
-        for workout_template_id in workout_template_ids {
-            let details =
-                user_workout_template_details(&self.0.db, user_id, workout_template_id).await?;
-            let exp = ImportOrExportWorkoutTemplateItem {
-                details: details.details,
-                collections: details.collections.into_iter().map(|c| c.name).collect(),
-            };
-            writer.serialize_value(&exp).unwrap();
+            ryot_log!(
+                debug,
+                "Exporting workout templates list page: {current_page}"
+            );
+            for workout_template_id in workout_template_ids.items {
+                let details =
+                    user_workout_template_details(&self.0.db, user_id, workout_template_id).await?;
+                let exp = ImportOrExportWorkoutTemplateItem {
+                    details: details.details,
+                    collections: details.collections.into_iter().map(|c| c.name).collect(),
+                };
+                writer.serialize_value(&exp).unwrap();
+            }
+            if let Some(next_page) = workout_template_ids.details.next_page {
+                current_page = next_page;
+            } else {
+                break;
+            }
         }
         Ok(())
     }
@@ -453,10 +541,10 @@ impl ExporterService {
                 false => Some(rev.comments),
             },
             review: Some(ImportOrExportItemReview {
-                visibility: Some(rev.visibility),
+                text: rev.text_original,
                 date: Some(rev.posted_on),
                 spoiler: Some(rev.is_spoiler),
-                text: rev.text_original,
+                visibility: Some(rev.visibility),
             }),
         }
     }
diff --git a/crates/services/fitness/Cargo.toml b/crates/services/fitness/Cargo.toml
index 6c7492a596..81ce5f6b8c 100644
--- a/crates/services/fitness/Cargo.toml
+++ b/crates/services/fitness/Cargo.toml
@@ -17,12 +17,10 @@ dependent-utils = { path = "../../utils/dependent" }
 enum-models = { path = "../../models/enum" }
 fitness-models = { path = "../../models/fitness" }
 futures = { workspace = true }
-migrations = { path = "../../migrations" }
 nanoid = { workspace = true }
 reqwest = { workspace = true }
 sea-orm = { workspace = true }
 sea-query = { workspace = true }
-slug = { workspace = true }
 supporting-service = { path = "../supporting" }
 tracing = { workspace = true }
 
diff --git a/crates/services/fitness/src/lib.rs b/crates/services/fitness/src/lib.rs
index f6b664561e..4486ed34cc 100644
--- a/crates/services/fitness/src/lib.rs
+++ b/crates/services/fitness/src/lib.rs
@@ -3,17 +3,15 @@ use std::sync::Arc;
 use application_utils::GraphqlRepresentation;
 use async_graphql::{Error, Result};
 use background_models::{ApplicationJob, MpApplicationJob};
-use common_models::{SearchDetails, SearchInput, StoredUrl};
-use common_utils::{ryot_log, PAGE_SIZE};
+use common_models::{SearchInput, StoredUrl};
+use common_utils::ryot_log;
 use database_models::{
-    collection_to_entity, exercise,
-    prelude::{
-        CollectionToEntity, Exercise, User, UserMeasurement, UserToEntity, Workout, WorkoutTemplate,
-    },
+    exercise,
+    prelude::{Exercise, User, UserMeasurement, UserToEntity, Workout, WorkoutTemplate},
     user, user_measurement, user_to_entity, workout, workout_template,
 };
 use database_utils::{
-    entity_in_collections, ilike_sql, item_reviews, schedule_user_for_workout_revision,
+    entity_in_collections, item_reviews, schedule_user_for_workout_revision,
     server_key_validation_guard, user_measurements_list, user_workout_details,
     user_workout_template_details,
 };
@@ -23,27 +21,23 @@ use dependent_models::{
 };
 use dependent_utils::{
     create_custom_exercise, create_or_update_user_workout, create_user_measurement,
-    db_workout_to_workout_input, get_focused_workout_summary,
+    db_workout_to_workout_input, exercises_list, get_focused_workout_summary,
+    user_workout_templates_list, user_workouts_list,
 };
 use enum_models::{EntityLot, ExerciseLot, ExerciseSource};
 use fitness_models::{
-    ExerciseAttributes, ExerciseCategory, ExerciseListItem, ExerciseSortBy, ExercisesListInput,
-    GithubExercise, GithubExerciseAttributes, ProcessedExercise, UpdateUserExerciseSettings,
+    ExerciseAttributes, ExerciseCategory, ExercisesListInput, GithubExercise,
+    GithubExerciseAttributes, ProcessedExercise, UpdateUserExerciseSettings,
     UpdateUserWorkoutAttributesInput, UserMeasurementsListInput, UserToExerciseExtraInformation,
     UserWorkoutInput, WorkoutInformation, WorkoutSetRecord, WorkoutSummary, WorkoutSummaryExercise,
 };
 use futures::TryStreamExt;
-use migrations::AliasedExercise;
 use nanoid::nanoid;
 use sea_orm::{
-    prelude::DateTimeUtc, ActiveModelTrait, ActiveValue, ColumnTrait, EntityTrait,
-    ItemsAndPagesNumber, ModelTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect,
-    QueryTrait, RelationTrait,
+    prelude::DateTimeUtc, ActiveModelTrait, ActiveValue, ColumnTrait, EntityTrait, ModelTrait,
+    PaginatorTrait, QueryFilter, QueryOrder,
 };
-use sea_query::{
-    extension::postgres::PgExpr, Alias, Condition, Expr, Func, JoinType, OnConflict, PgFunc,
-};
-use slug::slugify;
+use sea_query::{Expr, OnConflict};
 use supporting_service::SupportingService;
 
 const EXERCISE_DB_URL: &str = "https://raw.githubusercontent.com/yuhonas/free-exercise-db/main";
@@ -58,33 +52,7 @@ impl FitnessService {
         user_id: String,
         input: SearchInput,
     ) -> Result<SearchResults<String>> {
-        let page = input.page.unwrap_or(1);
-        let paginator = WorkoutTemplate::find()
-            .select_only()
-            .column(workout_template::Column::Id)
-            .filter(workout_template::Column::UserId.eq(user_id))
-            .apply_if(input.query, |query, v| {
-                query.filter(Expr::col(workout_template::Column::Name).ilike(ilike_sql(&v)))
-            })
-            .order_by_desc(workout_template::Column::CreatedOn)
-            .into_tuple::<String>()
-            .paginate(&self.0.db, PAGE_SIZE.try_into().unwrap());
-        let ItemsAndPagesNumber {
-            number_of_items,
-            number_of_pages,
-        } = paginator.num_items_and_pages().await?;
-        let items = paginator.fetch_page((page - 1).try_into().unwrap()).await?;
-        Ok(SearchResults {
-            details: SearchDetails {
-                total: number_of_items.try_into().unwrap(),
-                next_page: if page < number_of_pages.try_into().unwrap() {
-                    Some(page + 1)
-                } else {
-                    None
-                },
-            },
-            items,
-        })
+        user_workout_templates_list(&user_id, input, &self.0).await
     }
 
     pub async fn user_workout_template_details(
@@ -256,153 +224,15 @@ impl FitnessService {
         user_id: String,
         input: SearchInput,
     ) -> Result<SearchResults<String>> {
-        let page = input.page.unwrap_or(1);
-        let paginator = Workout::find()
-            .select_only()
-            .column(workout::Column::Id)
-            .filter(workout::Column::UserId.eq(user_id))
-            .apply_if(input.query, |query, v| {
-                query.filter(Expr::col(workout::Column::Name).ilike(ilike_sql(&v)))
-            })
-            .order_by_desc(workout::Column::EndTime)
-            .into_tuple::<String>()
-            .paginate(&self.0.db, PAGE_SIZE.try_into().unwrap());
-        let ItemsAndPagesNumber {
-            number_of_items,
-            number_of_pages,
-        } = paginator.num_items_and_pages().await?;
-        let items = paginator.fetch_page((page - 1).try_into().unwrap()).await?;
-        Ok(SearchResults {
-            details: SearchDetails {
-                total: number_of_items.try_into().unwrap(),
-                next_page: if page < number_of_pages.try_into().unwrap() {
-                    Some(page + 1)
-                } else {
-                    None
-                },
-            },
-            items,
-        })
+        user_workouts_list(&user_id, input, &self.0).await
     }
 
     pub async fn exercises_list(
         &self,
         user_id: String,
         input: ExercisesListInput,
-    ) -> Result<SearchResults<ExerciseListItem>> {
-        let page = input.search.page.unwrap_or(1);
-        let ex = Alias::new("exercise");
-        let etu = Alias::new("user_to_entity");
-        let order_by_col = match input.sort_by {
-            None => Expr::col((ex, exercise::Column::Id)),
-            Some(sb) => match sb {
-                // DEV: This is just a small hack to reduce duplicated code. We
-                // are ordering by name for the other `sort_by` anyway.
-                ExerciseSortBy::Name => Expr::val("1"),
-                ExerciseSortBy::TimesPerformed => Expr::expr(Func::coalesce([
-                    Expr::col((
-                        etu.clone(),
-                        user_to_entity::Column::ExerciseNumTimesInteracted,
-                    ))
-                    .into(),
-                    Expr::val(0).into(),
-                ])),
-                ExerciseSortBy::LastPerformed => Expr::expr(Func::coalesce([
-                    Expr::col((etu.clone(), user_to_entity::Column::LastUpdatedOn)).into(),
-                    // DEV: For some reason this does not work without explicit casting on postgres
-                    Func::cast_as(Expr::val("1900-01-01"), Alias::new("timestamptz")).into(),
-                ])),
-            },
-        };
-        let paginator = Exercise::find()
-            .column_as(
-                Expr::col((
-                    etu.clone(),
-                    user_to_entity::Column::ExerciseNumTimesInteracted,
-                )),
-                "num_times_interacted",
-            )
-            .column_as(
-                Expr::col((etu, user_to_entity::Column::LastUpdatedOn)),
-                "last_updated_on",
-            )
-            .filter(
-                exercise::Column::Source
-                    .eq(ExerciseSource::Github)
-                    .or(exercise::Column::CreatedByUserId.eq(&user_id)),
-            )
-            .apply_if(input.filter, |query, q| {
-                query
-                    .apply_if(q.lot, |q, v| q.filter(exercise::Column::Lot.eq(v)))
-                    .apply_if(q.muscle, |q, v| {
-                        q.filter(Expr::val(v).eq(PgFunc::any(Expr::col(exercise::Column::Muscles))))
-                    })
-                    .apply_if(q.level, |q, v| q.filter(exercise::Column::Level.eq(v)))
-                    .apply_if(q.force, |q, v| q.filter(exercise::Column::Force.eq(v)))
-                    .apply_if(q.mechanic, |q, v| {
-                        q.filter(exercise::Column::Mechanic.eq(v))
-                    })
-                    .apply_if(q.equipment, |q, v| {
-                        q.filter(exercise::Column::Equipment.eq(v))
-                    })
-                    .apply_if(q.collection, |q, v| {
-                        q.left_join(CollectionToEntity)
-                            .filter(collection_to_entity::Column::CollectionId.eq(v))
-                    })
-            })
-            .apply_if(input.search.query, |query, v| {
-                query.filter(
-                    Condition::any()
-                        .add(
-                            Expr::col((AliasedExercise::Table, AliasedExercise::Id))
-                                .ilike(ilike_sql(&v)),
-                        )
-                        .add(Expr::col(exercise::Column::Name).ilike(slugify(v))),
-                )
-            })
-            .join(
-                JoinType::LeftJoin,
-                user_to_entity::Relation::Exercise
-                    .def()
-                    .rev()
-                    .on_condition(move |_left, right| {
-                        Condition::all()
-                            .add(Expr::col((right, user_to_entity::Column::UserId)).eq(&user_id))
-                    }),
-            )
-            .order_by_desc(order_by_col)
-            .order_by_asc(exercise::Column::Id)
-            .into_model::<ExerciseListItem>()
-            .paginate(&self.0.db, PAGE_SIZE.try_into().unwrap());
-        let ItemsAndPagesNumber {
-            number_of_items,
-            number_of_pages,
-        } = paginator.num_items_and_pages().await?;
-        let mut items = vec![];
-        for ex in paginator.fetch_page((page - 1).try_into().unwrap()).await? {
-            let mut converted_exercise = ex.clone();
-            if let Some(img) = ex.attributes.internal_images.first() {
-                converted_exercise.image = Some(
-                    self.0
-                        .file_storage_service
-                        .get_stored_asset(img.clone())
-                        .await,
-                )
-            }
-            converted_exercise.muscle = ex.muscles.first().cloned();
-            items.push(converted_exercise);
-        }
-        Ok(SearchResults {
-            details: SearchDetails {
-                total: number_of_items.try_into().unwrap(),
-                next_page: if page < number_of_pages.try_into().unwrap() {
-                    Some(page + 1)
-                } else {
-                    None
-                },
-            },
-            items,
-        })
+    ) -> Result<SearchResults<String>> {
+        exercises_list(&user_id, input, &self.0).await
     }
 
     pub async fn deploy_update_exercise_library_job(&self) -> Result<()> {
diff --git a/crates/services/importer/src/goodreads.rs b/crates/services/importer/src/goodreads.rs
index 20b7c954a7..6ec20625cd 100644
--- a/crates/services/importer/src/goodreads.rs
+++ b/crates/services/importer/src/goodreads.rs
@@ -139,7 +139,6 @@ pub async fn import(
                 rating,
                 ..Default::default()
             }],
-            ..Default::default()
         }));
     }
     Ok(ImportResult { completed, failed })
diff --git a/crates/services/importer/src/imdb.rs b/crates/services/importer/src/imdb.rs
index 463d0b452a..84afe31de2 100644
--- a/crates/services/importer/src/imdb.rs
+++ b/crates/services/importer/src/imdb.rs
@@ -66,9 +66,9 @@ pub async fn import(
             Err(e) => {
                 failed.push(ImportFailedItem {
                     lot: Some(lot),
-                    step: ImportFailStep::MediaDetailsFromProvider,
                     identifier: record.id.clone(),
-                    error: Some(e.to_string()),
+                    step: ImportFailStep::ItemDetailsFromSource,
+                    error: Some(format!("Could not fetch details from TMDB: {e}")),
                 });
                 continue;
             }
diff --git a/crates/services/importer/src/lib.rs b/crates/services/importer/src/lib.rs
index 0a0361a4e7..5d8290a2ea 100644
--- a/crates/services/importer/src/lib.rs
+++ b/crates/services/importer/src/lib.rs
@@ -4,7 +4,7 @@ use async_graphql::Result;
 use background_models::{ApplicationJob, MpApplicationJob};
 use chrono::{DateTime, Duration, NaiveDateTime, Offset, TimeZone, Utc};
 use common_models::BackgroundJob;
-use common_utils::ryot_log;
+use common_utils::{ryot_log, MAX_IMPORT_RETRIES_FOR_PARTIAL_STATE};
 use database_models::{
     exercise, import_report,
     prelude::{Exercise, ImportReport},
@@ -72,10 +72,12 @@ impl ImporterService {
         user_id: String,
         input: Box<DeployImportJobInput>,
     ) -> Result<()> {
+        let import_started_at = Utc::now();
         let model = import_report::ActiveModel {
             source: ActiveValue::Set(input.source),
             progress: ActiveValue::Set(Some(dec!(0))),
             user_id: ActiveValue::Set(user_id.to_owned()),
+            estimated_finish_time: ActiveValue::Set(import_started_at + Duration::hours(1)),
             ..Default::default()
         };
         let db_import_job = model.insert(&self.0.db).await.unwrap();
@@ -143,7 +145,16 @@ impl ImporterService {
         let mut model: import_report::ActiveModel = db_import_job.into();
         match maybe_import {
             Ok(import) => {
-                match process_import(&user_id, true, false, import, &self.0, |progress| {
+                let mut quick_update_model = model.clone();
+                let each_item = (1..MAX_IMPORT_RETRIES_FOR_PARTIAL_STATE + 1)
+                    .map(|i| usize::pow(2, i as u32))
+                    .sum::<usize>();
+                quick_update_model.estimated_finish_time = ActiveValue::Set(
+                    import_started_at
+                        + Duration::seconds((import.completed.len() * each_item) as i64),
+                );
+                quick_update_model.update(&self.0.db).await?;
+                match process_import(&user_id, false, import, &self.0, |progress| {
                     let id = import_id.clone();
                     async move {
                         ImportReport::update_many()
diff --git a/crates/services/importer/src/mediatracker.rs b/crates/services/importer/src/mediatracker.rs
index cab1e19787..577a551f73 100644
--- a/crates/services/importer/src/mediatracker.rs
+++ b/crates/services/importer/src/mediatracker.rs
@@ -202,7 +202,7 @@ pub async fn import(input: DeployUrlAndKeyImportInput) -> Result<ImportResult> {
         let details: ItemDetails = match rsp.json().await {
             Ok(s) => s,
             Err(e) => {
-                ryot_log!(error, "Encountered error for id = {id:?}: {e:?}", id = d.id);
+                ryot_log!(debug, "Encountered error for id = {id:?}: {e:?}", id = d.id);
                 failed.push(ImportFailedItem {
                     lot: Some(lot),
                     step: ImportFailStep::ItemDetailsFromSource,
@@ -300,7 +300,6 @@ pub async fn import(input: DeployUrlAndKeyImportInput) -> Result<ImportResult> {
                     }
                 })
                 .collect(),
-            ..Default::default()
         };
         completed.push(ImportCompletedItem::Metadata(item));
     }
diff --git a/crates/services/importer/src/storygraph.rs b/crates/services/importer/src/storygraph.rs
index de664444c5..092ea89789 100644
--- a/crates/services/importer/src/storygraph.rs
+++ b/crates/services/importer/src/storygraph.rs
@@ -145,7 +145,6 @@ pub async fn import(
                 }),
                 ..Default::default()
             }],
-            ..Default::default()
         })
     }
     Ok(ImportResult {
diff --git a/crates/services/integration/src/lib.rs b/crates/services/integration/src/lib.rs
index 0c9844f4f0..bc123e9d3b 100644
--- a/crates/services/integration/src/lib.rs
+++ b/crates/services/integration/src/lib.rs
@@ -65,14 +65,9 @@ impl IntegrationService {
                 });
             }
         });
-        match process_import(
-            &integration.user_id,
-            false,
-            true,
-            import,
-            &self.0,
-            |_| async { Ok(()) },
-        )
+        match process_import(&integration.user_id, true, import, &self.0, |_| async {
+            Ok(())
+        })
         .await
         {
             Ok(_) => {
diff --git a/crates/services/integration/src/yank/komga.rs b/crates/services/integration/src/yank/komga.rs
index b509dd2250..c1fb4b7fb4 100644
--- a/crates/services/integration/src/yank/komga.rs
+++ b/crates/services/integration/src/yank/komga.rs
@@ -408,7 +408,7 @@ pub async fn yank_progress(
         mutex_task.get_or_init(|| {
             tokio::spawn(async move {
                 if let Err(e) = sse_listener(tx, base_url, komga_username, komga_password).await {
-                    ryot_log!(error, "SSE listener error: {}", e);
+                    ryot_log!(debug, "SSE listener error: {}", e);
                 }
             });
         });
diff --git a/crates/services/miscellaneous/src/lib.rs b/crates/services/miscellaneous/src/lib.rs
index b806d7b6e1..1c779aace2 100644
--- a/crates/services/miscellaneous/src/lib.rs
+++ b/crates/services/miscellaneous/src/lib.rs
@@ -6,11 +6,10 @@ use std::{
 
 use application_utils::{
     get_current_date, get_podcast_episode_by_number, get_show_episode_by_numbers,
-    graphql_to_db_order,
 };
 use async_graphql::{Error, Result};
 use background_models::{ApplicationJob, HpApplicationJob, MpApplicationJob};
-use chrono::{Days, Duration, NaiveDate, Utc};
+use chrono::{Days, NaiveDate, Utc};
 use common_models::{
     ApplicationCacheKey, BackgroundJob, ChangeCollectionToEntityInput, DefaultCollection,
     IdAndNamedObject, MetadataGroupSearchInput, MetadataSearchInput, PeopleSearchInput,
@@ -35,7 +34,7 @@ use database_models::{
     review, seen, user, user_notification, user_to_entity,
 };
 use database_utils::{
-    apply_collection_filter, calculate_user_activities_and_summary, entity_in_collections,
+    calculate_user_activities_and_summary, entity_in_collections,
     entity_in_collections_with_collection_to_entity_ids, ilike_sql, item_reviews,
     revoke_access_link, user_by_id,
 };
@@ -52,8 +51,8 @@ use dependent_utils::{
     first_metadata_image_as_url, get_entity_recently_consumed, get_metadata_provider,
     get_openlibrary_service, get_tmdb_non_media_service, get_users_and_cte_monitoring_entity,
     get_users_monitoring_entity, handle_after_media_seen_tasks, is_metadata_finished_by_user,
-    metadata_images_as_urls, post_review, progress_update,
-    refresh_collection_to_entity_association, remove_entity_from_collection,
+    metadata_groups_list, metadata_images_as_urls, metadata_list, people_list, post_review,
+    progress_update, refresh_collection_to_entity_association, remove_entity_from_collection,
     update_metadata_and_notify_users,
 };
 use enum_models::{
@@ -68,20 +67,18 @@ use media_models::{
     CreateReviewCommentInput, GenreDetailsInput, GenreListItem, GraphqlCalendarEvent,
     GraphqlMediaAssets, GraphqlMetadataDetails, GraphqlMetadataGroup, GraphqlVideoAsset,
     GroupedCalendarEvent, ImportOrExportItemReviewComment, MarkEntityAsPartialInput,
-    MediaAssociatedPersonStateChanges, MediaGeneralFilter, MediaSortBy, MetadataCreator,
-    MetadataCreatorGroupedByRole, MetadataFreeCreator, MetadataGroupsListInput, MetadataImage,
-    MetadataListInput, MetadataPartialDetails, MetadataVideo, MetadataVideoSource, PartialMetadata,
-    PartialMetadataWithoutId, PeopleListInput, PersonAndMetadataGroupsSortBy,
-    PersonDetailsGroupedByRole, PersonDetailsItemWithCharacter, PodcastSpecifics,
-    ProgressUpdateInput, ReviewPostedEvent, SeenAnimeExtraInformation, SeenPodcastExtraInformation,
-    SeenShowExtraInformation, ShowSpecifics, UniqueMediaIdentifier, UpdateCustomMetadataInput,
-    UpdateSeenItemInput, UserCalendarEventInput, UserMediaNextEntry,
-    UserMetadataDetailsEpisodeProgress, UserMetadataDetailsShowSeasonProgress,
-    UserUpcomingCalendarEventInput,
+    MediaAssociatedPersonStateChanges, MetadataCreator, MetadataCreatorGroupedByRole,
+    MetadataFreeCreator, MetadataGroupsListInput, MetadataImage, MetadataListInput,
+    MetadataPartialDetails, MetadataVideo, MetadataVideoSource, PartialMetadata,
+    PartialMetadataWithoutId, PeopleListInput, PersonDetailsGroupedByRole,
+    PersonDetailsItemWithCharacter, PodcastSpecifics, ProgressUpdateInput, ReviewPostedEvent,
+    SeenAnimeExtraInformation, SeenPodcastExtraInformation, SeenShowExtraInformation,
+    ShowSpecifics, UniqueMediaIdentifier, UpdateCustomMetadataInput, UpdateSeenItemInput,
+    UserCalendarEventInput, UserMediaNextEntry, UserMetadataDetailsEpisodeProgress,
+    UserMetadataDetailsShowSeasonProgress, UserUpcomingCalendarEventInput,
 };
 use migrations::{
-    AliasedCalendarEvent, AliasedMetadata, AliasedMetadataToGenre, AliasedReview, AliasedSeen,
-    AliasedUserToEntity,
+    AliasedCalendarEvent, AliasedMetadata, AliasedMetadataToGenre, AliasedSeen, AliasedUserToEntity,
 };
 use nanoid::nanoid;
 use notification_service::send_notification;
@@ -94,19 +91,19 @@ use providers::{
 use rust_decimal::Decimal;
 use rust_decimal_macros::dec;
 use sea_orm::{
-    prelude::DateTimeUtc, query::UpdateMany, sea_query::NullOrdering, ActiveModelTrait,
-    ActiveValue, ColumnTrait, ConnectionTrait, DatabaseBackend, DatabaseConnection, EntityTrait,
-    FromQueryResult, ItemsAndPagesNumber, JoinType, ModelTrait, Order, PaginatorTrait, QueryFilter,
-    QueryOrder, QuerySelect, QueryTrait, RelationTrait, Statement, TransactionTrait,
+    prelude::DateTimeUtc, query::UpdateMany, ActiveModelTrait, ActiveValue, ColumnTrait,
+    ConnectionTrait, DatabaseBackend, DatabaseConnection, EntityTrait, FromQueryResult,
+    ItemsAndPagesNumber, JoinType, ModelTrait, Order, PaginatorTrait, QueryFilter, QueryOrder,
+    QuerySelect, QueryTrait, RelationTrait, Statement, TransactionTrait,
 };
 use sea_query::{
-    extension::postgres::PgExpr, Alias, Asterisk, Cond, Condition, Expr, Func, PgFunc,
+    extension::postgres::PgExpr, Alias, Asterisk, Condition, Expr, Func, PgFunc,
     PostgresQueryBuilder, Query, SelectStatement,
 };
 use serde::{Deserialize, Serialize};
 use supporting_service::SupportingService;
 use traits::{MediaProvider, TraceOk};
-use user_models::{DashboardElementLot, UserReviewScale};
+use user_models::DashboardElementLot;
 use uuid::Uuid;
 
 static ENTITY_UPDATE_CHUNK_SIZE: usize = 5;
@@ -893,151 +890,7 @@ ORDER BY RANDOM() LIMIT 10;
         user_id: String,
         input: MetadataListInput,
     ) -> Result<SearchResults<String>> {
-        let preferences = user_by_id(&user_id, &self.0).await?.preferences;
-
-        let avg_rating_col = "user_average_rating";
-        let cloned_user_id_1 = user_id.clone();
-        let cloned_user_id_2 = user_id.clone();
-
-        let order_by = input
-            .sort
-            .clone()
-            .map(|a| graphql_to_db_order(a.order))
-            .unwrap_or(Order::Asc);
-        let review_scale = match preferences.general.review_scale {
-            UserReviewScale::OutOfFive => 20,
-            UserReviewScale::OutOfHundred | UserReviewScale::ThreePointSmiley => 1,
-        };
-        let take = input.take.unwrap_or(PAGE_SIZE as u64);
-        let page: u64 = input
-            .search
-            .clone()
-            .and_then(|s| s.page)
-            .unwrap_or(1)
-            .try_into()
-            .unwrap();
-        let paginator = Metadata::find()
-            .select_only()
-            .column(metadata::Column::Id)
-            .expr_as(
-                Func::round_with_precision(
-                    Func::avg(
-                        Expr::col((AliasedReview::Table, AliasedReview::Rating)).div(review_scale),
-                    ),
-                    review_scale,
-                ),
-                avg_rating_col,
-            )
-            .group_by(metadata::Column::Id)
-            .group_by(user_to_entity::Column::MediaReason)
-            .filter(user_to_entity::Column::UserId.eq(&user_id))
-            .apply_if(input.lot, |query, v| {
-                query.filter(metadata::Column::Lot.eq(v))
-            })
-            .inner_join(UserToEntity)
-            .join(
-                JoinType::LeftJoin,
-                metadata::Relation::Review
-                    .def()
-                    .on_condition(move |_left, right| {
-                        Condition::all().add(
-                            Expr::col((right, review::Column::UserId)).eq(cloned_user_id_1.clone()),
-                        )
-                    }),
-            )
-            .join(
-                JoinType::LeftJoin,
-                metadata::Relation::Seen
-                    .def()
-                    .on_condition(move |_left, right| {
-                        Condition::all().add(
-                            Expr::col((right, seen::Column::UserId)).eq(cloned_user_id_2.clone()),
-                        )
-                    }),
-            )
-            .apply_if(input.search.and_then(|s| s.query), |query, v| {
-                query.filter(
-                    Cond::any()
-                        .add(Expr::col(metadata::Column::Title).ilike(ilike_sql(&v)))
-                        .add(Expr::col(metadata::Column::Description).ilike(ilike_sql(&v))),
-                )
-            })
-            .apply_if(
-                input.filter.clone().and_then(|f| f.collections),
-                |query, v| {
-                    apply_collection_filter(
-                        query,
-                        Some(v),
-                        input.invert_collection,
-                        metadata::Column::Id,
-                        collection_to_entity::Column::MetadataId,
-                    )
-                },
-            )
-            .apply_if(input.filter.and_then(|f| f.general), |query, v| match v {
-                MediaGeneralFilter::All => query.filter(metadata::Column::Id.is_not_null()),
-                MediaGeneralFilter::Rated => query.filter(review::Column::Id.is_not_null()),
-                MediaGeneralFilter::Unrated => query.filter(review::Column::Id.is_null()),
-                MediaGeneralFilter::Unfinished => query.filter(
-                    Expr::expr(
-                        Expr::val(UserToMediaReason::Finished.to_string())
-                            .eq(PgFunc::any(Expr::col(user_to_entity::Column::MediaReason))),
-                    )
-                    .not(),
-                ),
-                s => query.filter(seen::Column::State.eq(match s {
-                    MediaGeneralFilter::Dropped => SeenState::Dropped,
-                    MediaGeneralFilter::OnAHold => SeenState::OnAHold,
-                    _ => unreachable!(),
-                })),
-            })
-            .apply_if(input.sort.map(|s| s.by), |query, v| match v {
-                MediaSortBy::LastUpdated => query
-                    .order_by(user_to_entity::Column::LastUpdatedOn, order_by)
-                    .group_by(user_to_entity::Column::LastUpdatedOn),
-                MediaSortBy::Title => query.order_by(metadata::Column::Title, order_by),
-                MediaSortBy::ReleaseDate => query.order_by_with_nulls(
-                    metadata::Column::PublishYear,
-                    order_by,
-                    NullOrdering::Last,
-                ),
-                MediaSortBy::LastSeen => query.order_by_with_nulls(
-                    seen::Column::FinishedOn.max(),
-                    order_by,
-                    NullOrdering::Last,
-                ),
-                MediaSortBy::UserRating => query.order_by_with_nulls(
-                    Expr::col(Alias::new(avg_rating_col)),
-                    order_by,
-                    NullOrdering::Last,
-                ),
-                MediaSortBy::ProviderRating => query.order_by_with_nulls(
-                    metadata::Column::ProviderRating,
-                    order_by,
-                    NullOrdering::Last,
-                ),
-            })
-            .into_tuple::<String>()
-            .paginate(&self.0.db, take);
-        let ItemsAndPagesNumber {
-            number_of_items,
-            number_of_pages,
-        } = paginator.num_items_and_pages().await?;
-        let mut items = vec![];
-        for c in paginator.fetch_page(page - 1).await? {
-            items.push(c);
-        }
-        Ok(SearchResults {
-            details: SearchDetails {
-                total: number_of_items.try_into().unwrap(),
-                next_page: if page < number_of_pages {
-                    Some((page + 1).try_into().unwrap())
-                } else {
-                    None
-                },
-            },
-            items,
-        })
+        metadata_list(&user_id, input, &self.0).await
     }
 
     pub async fn deploy_bulk_progress_update(
@@ -1076,7 +929,7 @@ ORDER BY RANDOM() LIMIT 10;
 
     pub async fn mark_entity_as_partial(
         &self,
-        _user_id: &String,
+        _user_id: &str,
         input: MarkEntityAsPartialInput,
     ) -> Result<bool> {
         match input.entity_lot {
@@ -1972,73 +1825,7 @@ ORDER BY RANDOM() LIMIT 10;
         user_id: String,
         input: MetadataGroupsListInput,
     ) -> Result<SearchResults<String>> {
-        let page: u64 = input
-            .search
-            .clone()
-            .and_then(|f| f.page)
-            .unwrap_or(1)
-            .try_into()
-            .unwrap();
-        let alias = "parts";
-        let media_items_col = Expr::col(Alias::new(alias));
-        let (order_by, sort_order) = match input.sort {
-            None => (media_items_col, Order::Desc),
-            Some(ord) => (
-                match ord.by {
-                    PersonAndMetadataGroupsSortBy::Name => Expr::col(metadata_group::Column::Title),
-                    PersonAndMetadataGroupsSortBy::MediaItems => media_items_col,
-                },
-                graphql_to_db_order(ord.order),
-            ),
-        };
-        let take = input.take.unwrap_or(PAGE_SIZE.try_into().unwrap());
-        let paginator = MetadataGroup::find()
-            .select_only()
-            .column(metadata_group::Column::Id)
-            .group_by(metadata_group::Column::Id)
-            .inner_join(UserToEntity)
-            .filter(user_to_entity::Column::UserId.eq(&user_id))
-            .filter(metadata_group::Column::Id.is_not_null())
-            .apply_if(input.search.and_then(|f| f.query), |query, v| {
-                query.filter(
-                    Condition::all()
-                        .add(Expr::col(metadata_group::Column::Title).ilike(ilike_sql(&v))),
-                )
-            })
-            .apply_if(
-                input.filter.clone().and_then(|f| f.collections),
-                |query, v| {
-                    apply_collection_filter(
-                        query,
-                        Some(v),
-                        input.invert_collection,
-                        metadata_group::Column::Id,
-                        collection_to_entity::Column::MetadataGroupId,
-                    )
-                },
-            )
-            .order_by(order_by, sort_order)
-            .into_tuple::<String>()
-            .paginate(&self.0.db, take);
-        let ItemsAndPagesNumber {
-            number_of_items,
-            number_of_pages,
-        } = paginator.num_items_and_pages().await?;
-        let mut items = vec![];
-        for c in paginator.fetch_page(page - 1).await? {
-            items.push(c);
-        }
-        Ok(SearchResults {
-            details: SearchDetails {
-                total: number_of_items.try_into().unwrap(),
-                next_page: if page < number_of_pages {
-                    Some((page + 1).try_into().unwrap())
-                } else {
-                    None
-                },
-            },
-            items,
-        })
+        metadata_groups_list(&user_id, &self.0, input).await
     }
 
     pub async fn people_list(
@@ -2046,78 +1833,7 @@ ORDER BY RANDOM() LIMIT 10;
         user_id: String,
         input: PeopleListInput,
     ) -> Result<SearchResults<String>> {
-        let page: u64 = input
-            .search
-            .clone()
-            .and_then(|f| f.page)
-            .unwrap_or(1)
-            .try_into()
-            .unwrap();
-        let alias = "media_count";
-        let media_items_col = Expr::col(Alias::new(alias));
-        let (order_by, sort_order) = match input.sort {
-            None => (media_items_col, Order::Desc),
-            Some(ord) => (
-                match ord.by {
-                    PersonAndMetadataGroupsSortBy::Name => Expr::col(person::Column::Name),
-                    PersonAndMetadataGroupsSortBy::MediaItems => media_items_col,
-                },
-                graphql_to_db_order(ord.order),
-            ),
-        };
-        let take = input.take.unwrap_or(PAGE_SIZE.try_into().unwrap());
-        let creators_paginator = Person::find()
-            .apply_if(input.search.clone().and_then(|s| s.query), |query, v| {
-                query.filter(
-                    Condition::all().add(Expr::col(person::Column::Name).ilike(ilike_sql(&v))),
-                )
-            })
-            .apply_if(
-                input.filter.clone().and_then(|f| f.collections),
-                |query, v| {
-                    apply_collection_filter(
-                        query,
-                        Some(v),
-                        input.invert_collection,
-                        person::Column::Id,
-                        collection_to_entity::Column::PersonId,
-                    )
-                },
-            )
-            .column_as(
-                Expr::expr(Func::count(Expr::col((
-                    Alias::new("metadata_to_person"),
-                    metadata_to_person::Column::MetadataId,
-                )))),
-                alias,
-            )
-            .filter(user_to_entity::Column::UserId.eq(user_id))
-            .left_join(MetadataToPerson)
-            .inner_join(UserToEntity)
-            .group_by(person::Column::Id)
-            .group_by(person::Column::Name)
-            .order_by(order_by, sort_order)
-            .into_tuple::<String>()
-            .paginate(&self.0.db, take);
-        let ItemsAndPagesNumber {
-            number_of_items,
-            number_of_pages,
-        } = creators_paginator.num_items_and_pages().await?;
-        let mut creators = vec![];
-        for cr in creators_paginator.fetch_page(page - 1).await? {
-            creators.push(cr);
-        }
-        Ok(SearchResults {
-            details: SearchDetails {
-                total: number_of_items.try_into().unwrap(),
-                next_page: if page < number_of_pages {
-                    Some((page + 1).try_into().unwrap())
-                } else {
-                    None
-                },
-            },
-            items: creators,
-        })
+        people_list(&user_id, input, &self.0).await
     }
 
     pub async fn person_details(&self, person_id: String) -> Result<GraphqlPersonDetails> {
@@ -2349,11 +2065,15 @@ ORDER BY RANDOM() LIMIT 10;
     pub async fn recalculate_calendar_events(&self) -> Result<()> {
         let date_to_calculate_from = get_current_date(&self.0.timezone).pred_opt().unwrap();
 
-        let mut meta_stream = Metadata::find()
+        let selected_metadata = Metadata::find()
             .filter(metadata::Column::LastUpdatedOn.gte(date_to_calculate_from))
-            .filter(metadata::Column::IsPartial.eq(false))
-            .stream(&self.0.db)
-            .await?;
+            .filter(
+                metadata::Column::IsPartial
+                    .eq(false)
+                    .or(metadata::Column::IsPartial.is_null()),
+            );
+
+        let mut meta_stream = selected_metadata.clone().stream(&self.0.db).await?;
 
         while let Some(meta) = meta_stream.try_next().await? {
             ryot_log!(trace, "Processing metadata id = {:#?}", meta.id);
@@ -2417,16 +2137,8 @@ ORDER BY RANDOM() LIMIT 10;
 
         ryot_log!(debug, "Finished deleting invalid calendar events");
 
-        let mut metadata_stream = Metadata::find()
-            .filter(metadata::Column::LastUpdatedOn.gte(date_to_calculate_from))
-            .filter(
-                metadata::Column::IsPartial
-                    .is_null()
-                    .or(metadata::Column::IsPartial.eq(false)),
-            )
-            .order_by_desc(metadata::Column::LastUpdatedOn)
-            .stream(&self.0.db)
-            .await?;
+        let mut metadata_stream = selected_metadata.stream(&self.0.db).await?;
+
         let mut calendar_events_inserts = vec![];
         let mut metadata_updates = vec![];
         while let Some(meta) = metadata_stream.try_next().await? {
@@ -2818,10 +2530,13 @@ ORDER BY RANDOM() LIMIT 10;
     }
 
     async fn invalidate_import_jobs(&self) -> Result<()> {
-        let threshold = Utc::now() - Duration::hours(24);
         let all_jobs = ImportReport::find()
-            .filter(import_report::Column::WasSuccess.is_null())
-            .filter(import_report::Column::StartedOn.lt(threshold))
+            .filter(
+                import_report::Column::WasSuccess
+                    .eq(false)
+                    .or(import_report::Column::WasSuccess.is_null()),
+            )
+            .filter(import_report::Column::EstimatedFinishTime.lt(Utc::now()))
             .all(&self.0.db)
             .await?;
         for job in all_jobs {
diff --git a/crates/utils/common/src/lib.rs b/crates/utils/common/src/lib.rs
index 956e78d1ab..31bba0a958 100644
--- a/crates/utils/common/src/lib.rs
+++ b/crates/utils/common/src/lib.rs
@@ -21,6 +21,7 @@ pub const USER_AGENT_STR: &str = const_str::concat!(
     ")"
 );
 pub const COMPILATION_TIMESTAMP: i64 = compile_time::unix!();
+pub const MAX_IMPORT_RETRIES_FOR_PARTIAL_STATE: usize = 5;
 pub const AVATAR_URL: &str =
     "https://raw.githubusercontent.com/IgnisDa/ryot/main/libs/assets/icon-512x512.png";
 #[cfg(not(debug_assertions))]
diff --git a/crates/utils/dependent/Cargo.toml b/crates/utils/dependent/Cargo.toml
index b53d532318..f50bc5bea9 100644
--- a/crates/utils/dependent/Cargo.toml
+++ b/crates/utils/dependent/Cargo.toml
@@ -20,13 +20,16 @@ fitness-models = { path = "../../models/fitness" }
 importer-models = { path = "../../models/importer" }
 itertools = { workspace = true }
 media-models = { path = "../../models/media" }
+migrations = { path = "../../migrations" }
 nanoid = { workspace = true }
 providers = { path = "../../providers" }
+rand = { workspace = true }
 rust_decimal = { workspace = true }
 rust_decimal_macros = { workspace = true }
 sea-orm = { workspace = true }
 serde = { workspace = true }
 serde_json = { workspace = true }
+slug = { workspace = true }
 sqlx = { workspace = true }
 supporting-service = { path = "../../services/supporting" }
 tracing = { workspace = true }
diff --git a/crates/utils/dependent/src/lib.rs b/crates/utils/dependent/src/lib.rs
index b369d03300..21f8430213 100644
--- a/crates/utils/dependent/src/lib.rs
+++ b/crates/utils/dependent/src/lib.rs
@@ -1,15 +1,18 @@
 use std::{cmp::Reverse, collections::HashMap, future::Future, iter::zip, sync::Arc};
 
-use application_utils::get_current_date;
+use application_utils::{get_current_date, graphql_to_db_order};
 use async_graphql::{Enum, Error, Result};
 use background_models::{ApplicationJob, HpApplicationJob, LpApplicationJob, MpApplicationJob};
 use chrono::Utc;
 use common_models::{
     ApplicationCacheKey, BackgroundJob, ChangeCollectionToEntityInput, DefaultCollection,
-    MetadataRecentlyConsumedCacheInput, ProgressUpdateCacheInput, StoredUrl, StringIdObject,
-    UserLevelCacheKey, UserNotificationContent,
+    MetadataRecentlyConsumedCacheInput, ProgressUpdateCacheInput, SearchDetails, SearchInput,
+    StoredUrl, StringIdObject, UserLevelCacheKey, UserNotificationContent,
+};
+use common_utils::{
+    acquire_lock, ryot_log, sleep_for_n_seconds, EXERCISE_LOT_MAPPINGS,
+    MAX_IMPORT_RETRIES_FOR_PARTIAL_STATE, PAGE_SIZE, SHOW_SPECIAL_SEASON_NAMES,
 };
-use common_utils::{acquire_lock, ryot_log, EXERCISE_LOT_MAPPINGS, SHOW_SPECIAL_SEASON_NAMES};
 use database_models::{
     collection, collection_to_entity, exercise,
     functions::associate_user_with_entity,
@@ -18,21 +21,25 @@ use database_models::{
     prelude::{
         Collection, CollectionToEntity, Exercise, Genre, Metadata, MetadataGroup, MetadataToGenre,
         MetadataToMetadata, MetadataToPerson, MonitoredEntity, Person, Seen, UserToEntity, Workout,
+        WorkoutTemplate,
     },
-    review, seen, user_measurement, user_notification, user_to_entity, workout,
+    review, seen, user_measurement, user_notification, user_to_entity, workout, workout_template,
 };
 use database_utils::{
-    admin_account_guard, get_cte_column_from_lot, schedule_user_for_workout_revision, user_by_id,
+    admin_account_guard, apply_collection_filter, get_cte_column_from_lot, ilike_sql,
+    schedule_user_for_workout_revision, user_by_id,
+};
+use dependent_models::{
+    ApplicationCacheValue, EmptyCacheValue, ImportCompletedItem, ImportResult, SearchResults,
 };
-use dependent_models::{ApplicationCacheValue, EmptyCacheValue, ImportCompletedItem, ImportResult};
 use enum_models::{
     EntityLot, ExerciseLot, ExerciseSource, MediaLot, MediaSource, MetadataToMetadataRelation,
-    SeenState, UserNotificationLot, Visibility, WorkoutSetPersonalBest,
+    SeenState, UserNotificationLot, UserToMediaReason, Visibility, WorkoutSetPersonalBest,
 };
 use file_storage_service::FileStorageService;
 use fitness_models::{
-    ExerciseBestSetRecord, ProcessedExercise, UserExerciseInput,
-    UserToExerciseBestSetExtraInformation, UserToExerciseExtraInformation,
+    ExerciseBestSetRecord, ExerciseSortBy, ExercisesListInput, ProcessedExercise,
+    UserExerciseInput, UserToExerciseBestSetExtraInformation, UserToExerciseExtraInformation,
     UserToExerciseHistoryExtraInformation, UserWorkoutInput, UserWorkoutSetRecord, WorkoutDuration,
     WorkoutEquipmentFocusedSummary, WorkoutFocusedSummary, WorkoutForceFocusedSummary,
     WorkoutInformation, WorkoutLevelFocusedSummary, WorkoutLotFocusedSummary,
@@ -43,13 +50,15 @@ use importer_models::{ImportDetails, ImportFailStep, ImportFailedItem, ImportRes
 use itertools::Itertools;
 use media_models::{
     CommitMediaInput, CommitPersonInput, CreateOrUpdateCollectionInput, CreateOrUpdateReviewInput,
-    ImportOrExportItemRating, MetadataDetails, MetadataImage, PartialMetadata,
-    PartialMetadataPerson, PartialMetadataWithoutId, ProgressUpdateError,
-    ProgressUpdateErrorVariant, ProgressUpdateInput, ProgressUpdateResultUnion, ReviewPostedEvent,
-    SeenAnimeExtraInformation, SeenMangaExtraInformation, SeenPodcastExtraInformation,
-    SeenPodcastExtraOptionalInformation, SeenShowExtraInformation,
-    SeenShowExtraOptionalInformation, UniqueMediaIdentifier,
+    ImportOrExportItemRating, MediaGeneralFilter, MediaSortBy, MetadataDetails,
+    MetadataGroupsListInput, MetadataImage, MetadataListInput, PartialMetadata,
+    PartialMetadataPerson, PartialMetadataWithoutId, PeopleListInput,
+    PersonAndMetadataGroupsSortBy, ProgressUpdateError, ProgressUpdateErrorVariant,
+    ProgressUpdateInput, ProgressUpdateResultUnion, ReviewPostedEvent, SeenAnimeExtraInformation,
+    SeenMangaExtraInformation, SeenPodcastExtraInformation, SeenPodcastExtraOptionalInformation,
+    SeenShowExtraInformation, SeenShowExtraOptionalInformation, UniqueMediaIdentifier,
 };
+use migrations::{AliasedExercise, AliasedReview};
 use nanoid::nanoid;
 use providers::{
     anilist::{AnilistAnimeService, AnilistMangaService},
@@ -65,6 +74,7 @@ use providers::{
     vndb::VndbService,
     youtube_music::YoutubeMusicService,
 };
+use rand::seq::SliceRandom;
 use rust_decimal::{
     prelude::{FromPrimitive, One, ToPrimitive},
     Decimal,
@@ -72,11 +82,13 @@ use rust_decimal::{
 use rust_decimal_macros::dec;
 use sea_orm::{
     prelude::{DateTimeUtc, Expr},
-    sea_query::OnConflict,
-    ActiveModelTrait, ActiveValue, ColumnTrait, DatabaseConnection, EntityTrait, Iterable,
-    ModelTrait, QueryFilter, QueryOrder, QuerySelect, QueryTrait, TransactionTrait,
+    sea_query::{extension::postgres::PgExpr, Alias, Func, NullOrdering, OnConflict, PgFunc},
+    ActiveModelTrait, ActiveValue, ColumnTrait, Condition, DatabaseConnection, EntityTrait,
+    ItemsAndPagesNumber, Iterable, JoinType, ModelTrait, Order, PaginatorTrait, QueryFilter,
+    QueryOrder, QuerySelect, QueryTrait, RelationTrait, TransactionTrait,
 };
 use serde::{Deserialize, Serialize};
+use slug::slugify;
 use supporting_service::SupportingService;
 use traits::{MediaProvider, TraceOk};
 use user_models::{UserPreferences, UserReviewScale};
@@ -1768,7 +1780,7 @@ pub async fn create_or_update_user_workout(
             .one(&ss.db)
             .await?
         else {
-            ryot_log!(error, "Exercise with id = {} not found", ex.exercise_id);
+            ryot_log!(debug, "Exercise with id = {} not found", ex.exercise_id);
             continue;
         };
         let mut sets = vec![];
@@ -1877,10 +1889,19 @@ pub async fn create_or_update_user_workout(
                     .one(&ss.db)
                     .await?
                 {
-                    let workout_set =
-                        workout.information.exercises[r.exercise_idx].sets[r.set_idx].clone();
-                    if get_personal_best(set, best_type)
-                        > get_personal_best(&workout_set, best_type)
+                    let workout_set = workout
+                        .information
+                        .exercises
+                        .get(r.exercise_idx)
+                        .and_then(|exercise| exercise.sets.get(r.set_idx));
+                    let workout_set = match workout_set {
+                        Some(s) => s,
+                        None => {
+                            ryot_log!(debug, "Workout set {} does not exist", r.set_idx);
+                            continue;
+                        }
+                    };
+                    if get_personal_best(set, best_type) > get_personal_best(workout_set, best_type)
                     {
                         if let Some(ref mut set_personal_bests) = set.personal_bests {
                             set_personal_bests.push(*best_type);
@@ -2015,13 +2036,14 @@ pub async fn create_or_update_user_workout(
 }
 
 async fn create_collection_and_add_entity_to_it(
-    ss: &Arc<SupportingService>,
     user_id: &String,
-    collection_name: String,
     entity_id: String,
     entity_lot: EntityLot,
-) -> Result<()> {
-    create_or_update_collection(
+    collection_name: String,
+    ss: &Arc<SupportingService>,
+    import_failed_set: &mut Vec<ImportFailedItem>,
+) {
+    if let Err(e) = create_or_update_collection(
         user_id,
         CreateOrUpdateCollectionInput {
             name: collection_name.clone(),
@@ -2029,8 +2051,16 @@ async fn create_collection_and_add_entity_to_it(
         },
         ss,
     )
-    .await?;
-    add_entity_to_collection(
+    .await
+    {
+        import_failed_set.push(ImportFailedItem {
+            identifier: collection_name.clone(),
+            step: ImportFailStep::DatabaseCommit,
+            error: Some(format!("Failed to create collection {}", e.message)),
+            ..Default::default()
+        });
+    }
+    if let Err(e) = add_entity_to_collection(
         user_id,
         ChangeCollectionToEntityInput {
             entity_id,
@@ -2042,8 +2072,14 @@ async fn create_collection_and_add_entity_to_it(
         ss,
     )
     .await
-    .ok();
-    Ok(())
+    {
+        import_failed_set.push(ImportFailedItem {
+            identifier: collection_name.clone(),
+            step: ImportFailStep::DatabaseCommit,
+            error: Some(format!("Failed to add entity to collection {}", e.message)),
+            ..Default::default()
+        });
+    };
 }
 
 pub fn generate_exercise_id(name: &str, lot: ExerciseLot, user_id: &str) -> String {
@@ -2087,7 +2123,6 @@ pub async fn create_custom_exercise(
 
 pub async fn process_import<F>(
     user_id: &String,
-    run_updates: bool,
     respect_cache: bool,
     mut import: ImportResult,
     ss: &Arc<SupportingService>,
@@ -2107,10 +2142,32 @@ where
         _ => true,
     });
 
-    for i in import.completed.iter_mut() {
-        match i {
+    import.completed.shuffle(&mut rand::rng());
+
+    // DEV: We need to make sure that exercises are created first because workouts are
+    // dependent on them.
+    import.completed.sort_by_key(|i| match i {
+        ImportCompletedItem::Exercise(_) => 0,
+        _ => 1,
+    });
+
+    let source_result = import.clone();
+    let total = import.completed.len();
+
+    let mut need_to_schedule_user_for_workout_revision = false;
+
+    for (idx, item) in import.completed.into_iter().enumerate() {
+        ryot_log!(
+            debug,
+            "Processing item ({}) {}/{}",
+            item.to_string(),
+            idx + 1,
+            total,
+        );
+        match item {
+            ImportCompletedItem::Empty => {}
             ImportCompletedItem::Metadata(metadata) => {
-                let db_metadata = match commit_metadata(
+                let db_metadata_id = match commit_metadata(
                     CommitMediaInput {
                         name: metadata.source_id.clone(),
                         unique: UniqueMediaIdentifier {
@@ -2123,125 +2180,48 @@ where
                 )
                 .await
                 {
-                    Ok(r) => r,
+                    Ok(m) => m.id,
                     Err(e) => {
                         import.failed.push(ImportFailedItem {
                             error: Some(e.message),
                             lot: Some(metadata.lot),
-                            identifier: metadata.source_id.to_owned(),
-                            step: ImportFailStep::MediaDetailsFromProvider,
+                            step: ImportFailStep::DatabaseCommit,
+                            identifier: metadata.source_id.to_string(),
                         });
                         continue;
                     }
                 };
-                if run_updates {
-                    deploy_update_metadata_job(&db_metadata.id, ss).await?;
-                }
-                metadata.id = db_metadata.id;
-            }
-            ImportCompletedItem::Person(person) => {
-                let db_person = match commit_person(
-                    CommitPersonInput {
-                        identifier: person.identifier.clone(),
-                        name: person.name.clone(),
-                        source: person.source,
-                        source_specifics: person.source_specifics.clone(),
-                    },
-                    &ss.db,
-                )
-                .await
-                {
-                    Ok(p) => p,
-                    Err(e) => {
-                        import.failed.push(ImportFailedItem {
-                            error: Some(e.message),
-                            identifier: person.identifier.clone(),
-                            step: ImportFailStep::MediaDetailsFromProvider,
-                            ..Default::default()
-                        });
-                        continue;
+                let mut was_updated_successfully = false;
+                for attempt in 0..MAX_IMPORT_RETRIES_FOR_PARTIAL_STATE {
+                    let is_partial = Metadata::find_by_id(&db_metadata_id)
+                        .select_only()
+                        .column(metadata::Column::IsPartial)
+                        .into_tuple::<bool>()
+                        .one(&ss.db)
+                        .await?
+                        .unwrap_or(true);
+                    if is_partial {
+                        deploy_update_metadata_job(&db_metadata_id, ss).await?;
+                        let sleep_time = u64::pow(2, (attempt + 1).try_into().unwrap());
+                        ryot_log!(debug, "Sleeping for {}s before metadata check", sleep_time);
+                        sleep_for_n_seconds(sleep_time).await;
+                    } else {
+                        was_updated_successfully = true;
+                        break;
                     }
-                };
-                if run_updates {
-                    ss.perform_application_job(ApplicationJob::Mp(MpApplicationJob::UpdatePerson(
-                        db_person.id.clone(),
-                    )))
-                    .await?;
                 }
-                person.id = db_person.id;
-            }
-            ImportCompletedItem::MetadataGroup(metadata_group) => {
-                let metadata_group_id = match commit_metadata_group(
-                    CommitMediaInput {
-                        name: metadata_group.title.clone(),
-                        unique: UniqueMediaIdentifier {
-                            lot: metadata_group.lot,
-                            source: metadata_group.source,
-                            identifier: metadata_group.identifier.clone(),
-                        },
-                    },
-                    ss,
-                )
-                .await
-                {
-                    Ok(r) => r.id,
-                    Err(e) => {
-                        import.failed.push(ImportFailedItem {
-                            error: Some(e.message),
-                            lot: Some(metadata_group.lot),
-                            identifier: metadata_group.title.to_owned(),
-                            step: ImportFailStep::MediaDetailsFromProvider,
-                        });
-                        continue;
-                    }
-                };
-                if run_updates {
-                    ss.perform_application_job(ApplicationJob::Mp(
-                        MpApplicationJob::UpdateMetadataGroup(metadata_group_id.clone()),
-                    ))
-                    .await?;
+                if !was_updated_successfully {
+                    import.failed.push(ImportFailedItem {
+                        lot: Some(metadata.lot),
+                        identifier: db_metadata_id.clone(),
+                        step: ImportFailStep::MediaDetailsFromProvider,
+                        error: Some("Progress update *might* be wrong".to_owned()),
+                    });
                 }
-                metadata_group.id = metadata_group_id;
-            }
-            _ => {}
-        }
-    }
-
-    // DEV: We need to sort the exercises to make sure they are created before the workouts
-    // because the workouts depend on the exercises.
-    import.completed.sort_by_key(|i| match i {
-        ImportCompletedItem::Exercise(_) => 0,
-        _ => 1,
-    });
-
-    let source_result = import.clone();
-    let total = import.completed.len();
-
-    let mut need_to_schedule_user_for_workout_revision = false;
-
-    for (idx, item) in import.completed.into_iter().enumerate() {
-        ryot_log!(
-            debug,
-            "Processing item ({}) {}/{}",
-            item.to_string(),
-            idx + 1,
-            total,
-        );
-        match item {
-            ImportCompletedItem::Empty => {}
-            ImportCompletedItem::Exercise(exercise) => {
-                create_custom_exercise(user_id, exercise, ss).await?;
-            }
-            ImportCompletedItem::Collection(col_details) => {
-                create_or_update_collection(user_id, col_details, ss).await?;
-            }
-            ImportCompletedItem::Metadata(metadata) => {
-                let db_metadata_id = metadata.id;
                 for seen in metadata.seen_history.iter() {
-                    let progress = if seen.progress.is_some() {
-                        seen.progress
-                    } else {
-                        Some(dec!(100))
+                    let progress = match seen.progress {
+                        Some(_p) => seen.progress,
+                        None => Some(dec!(100)),
                     };
                     if let Err(e) = progress_update(
                         user_id,
@@ -2266,7 +2246,7 @@ where
                     {
                         import.failed.push(ImportFailedItem {
                             lot: Some(metadata.lot),
-                            step: ImportFailStep::SeenHistoryConversion,
+                            step: ImportFailStep::DatabaseCommit,
                             identifier: metadata.source_id.to_owned(),
                             error: Some(e.message),
                         });
@@ -2282,7 +2262,7 @@ where
                         if let Err(e) = post_review(user_id, input, ss).await {
                             import.failed.push(ImportFailedItem {
                                 lot: Some(metadata.lot),
-                                step: ImportFailStep::ReviewConversion,
+                                step: ImportFailStep::DatabaseCommit,
                                 identifier: metadata.source_id.to_owned(),
                                 error: Some(e.message),
                             });
@@ -2291,17 +2271,45 @@ where
                 }
                 for col in metadata.collections.into_iter() {
                     create_collection_and_add_entity_to_it(
-                        ss,
                         user_id,
-                        col,
                         db_metadata_id.clone(),
                         EntityLot::Metadata,
+                        col,
+                        ss,
+                        &mut import.failed,
                     )
-                    .await?;
+                    .await;
                 }
             }
             ImportCompletedItem::MetadataGroup(metadata_group) => {
-                let db_metadata_group_id = metadata_group.id;
+                let db_metadata_group_id = match commit_metadata_group(
+                    CommitMediaInput {
+                        name: metadata_group.title.clone(),
+                        unique: UniqueMediaIdentifier {
+                            lot: metadata_group.lot,
+                            source: metadata_group.source,
+                            identifier: metadata_group.identifier.clone(),
+                        },
+                    },
+                    ss,
+                )
+                .await
+                {
+                    Ok(m) => m.id,
+                    Err(e) => {
+                        import.failed.push(ImportFailedItem {
+                            error: Some(e.message),
+                            lot: Some(metadata_group.lot),
+                            step: ImportFailStep::DatabaseCommit,
+                            identifier: metadata_group.title.to_string(),
+                        });
+                        continue;
+                    }
+                };
+                ss.perform_application_job(ApplicationJob::Mp(
+                    MpApplicationJob::UpdateMetadataGroup(db_metadata_group_id.clone()),
+                ))
+                .await?;
                 for review in metadata_group.reviews.iter() {
                     if let Some(input) = convert_review_into_input(
                         review,
@@ -2312,7 +2320,7 @@ where
                         if let Err(e) = post_review(user_id, input, ss).await {
                             import.failed.push(ImportFailedItem {
                                 lot: Some(metadata_group.lot),
-                                step: ImportFailStep::ReviewConversion,
+                                step: ImportFailStep::DatabaseCommit,
                                 identifier: metadata_group.title.to_owned(),
                                 error: Some(e.message),
                             });
@@ -2321,17 +2329,43 @@ where
                 }
                 for col in metadata_group.collections.into_iter() {
                     create_collection_and_add_entity_to_it(
-                        ss,
                         user_id,
-                        col,
                         db_metadata_group_id.clone(),
                         EntityLot::MetadataGroup,
+                        col,
+                        ss,
+                        &mut import.failed,
                     )
-                    .await?;
+                    .await;
                 }
             }
             ImportCompletedItem::Person(person) => {
-                let db_person_id = person.id;
+                let db_person_id = match commit_person(
+                    CommitPersonInput {
+                        source: person.source,
+                        name: person.name.clone(),
+                        identifier: person.identifier.clone(),
+                        source_specifics: person.source_specifics.clone(),
+                    },
+                    &ss.db,
+                )
+                .await
+                {
+                    Ok(p) => p.id,
+                    Err(e) => {
+                        import.failed.push(ImportFailedItem {
+                            error: Some(e.message),
+                            identifier: person.name.to_string(),
+                            step: ImportFailStep::DatabaseCommit,
+                            ..Default::default()
+                        });
+                        continue;
+                    }
+                };
+                ss.perform_application_job(ApplicationJob::Mp(MpApplicationJob::UpdatePerson(
+                    db_person_id.clone(),
+                )))
+                .await?;
                 for review in person.reviews.iter() {
                     if let Some(input) = convert_review_into_input(
                         review,
@@ -2343,7 +2377,7 @@ where
                             import.failed.push(ImportFailedItem {
                                 error: Some(e.message),
                                 identifier: person.name.to_owned(),
-                                step: ImportFailStep::ReviewConversion,
+                                step: ImportFailStep::DatabaseCommit,
                                 ..Default::default()
                             });
                         };
@@ -2351,62 +2385,90 @@ where
                 }
                 for col in person.collections.into_iter() {
                     create_collection_and_add_entity_to_it(
-                        ss,
                         user_id,
-                        col,
                         db_person_id.clone(),
                         EntityLot::Person,
+                        col,
+                        ss,
+                        &mut import.failed,
                     )
-                    .await?;
+                    .await;
+                }
+            }
+            ImportCompletedItem::Collection(col_details) => {
+                if let Err(e) = create_or_update_collection(user_id, col_details.clone(), ss).await
+                {
+                    import.failed.push(ImportFailedItem {
+                        error: Some(e.message),
+                        identifier: col_details.name.clone(),
+                        step: ImportFailStep::DatabaseCommit,
+                        ..Default::default()
+                    });
+                }
+            }
+            ImportCompletedItem::Exercise(exercise) => {
+                if let Err(e) = create_custom_exercise(user_id, exercise.clone(), ss).await {
+                    import.failed.push(ImportFailedItem {
+                        error: Some(e.message),
+                        identifier: exercise.name.clone(),
+                        step: ImportFailStep::DatabaseCommit,
+                        ..Default::default()
+                    });
                 }
             }
             ImportCompletedItem::Workout(workout) => {
                 need_to_schedule_user_for_workout_revision = true;
-                if let Err(err) = create_or_update_user_workout(user_id, workout, ss).await {
+                if let Err(err) = create_or_update_user_workout(user_id, workout.clone(), ss).await
+                {
                     import.failed.push(ImportFailedItem {
                         error: Some(err.message),
-                        identifier: "Exercise".to_string(),
-                        step: ImportFailStep::InputTransformation,
+                        identifier: workout.name,
+                        step: ImportFailStep::DatabaseCommit,
                         ..Default::default()
                     });
                 }
             }
             ImportCompletedItem::ApplicationWorkout(workout) => {
+                need_to_schedule_user_for_workout_revision = true;
                 let workout_input = db_workout_to_workout_input(workout.details);
-                match create_or_update_user_workout(user_id, workout_input, ss).await {
+                match create_or_update_user_workout(user_id, workout_input.clone(), ss).await {
                     Err(err) => {
                         import.failed.push(ImportFailedItem {
                             error: Some(err.message),
-                            identifier: "Exercise".to_string(),
-                            step: ImportFailStep::InputTransformation,
+                            identifier: workout_input.name,
+                            step: ImportFailStep::DatabaseCommit,
                             ..Default::default()
                         });
                     }
                     Ok(workout_id) => {
                         for col in workout.collections.into_iter() {
                             create_collection_and_add_entity_to_it(
-                                ss,
                                 user_id,
-                                col,
                                 workout_id.clone(),
                                 EntityLot::Workout,
+                                col,
+                                ss,
+                                &mut import.failed,
                             )
-                            .await?;
+                            .await;
                         }
                     }
                 }
             }
             ImportCompletedItem::Measurement(measurement) => {
-                if let Err(err) = create_user_measurement(user_id, measurement, &ss.db).await {
+                if let Err(err) =
+                    create_user_measurement(user_id, measurement.clone(), &ss.db).await
+                {
                     import.failed.push(ImportFailedItem {
                         error: Some(err.message),
-                        identifier: "Measurement".to_string(),
-                        step: ImportFailStep::InputTransformation,
+                        step: ImportFailStep::DatabaseCommit,
+                        identifier: measurement.timestamp.to_string(),
                         ..Default::default()
                     });
                 }
             }
         }
+
         on_item_processed(
             Decimal::from_usize(idx + 1).unwrap() / Decimal::from_usize(total).unwrap() * dec!(100),
         )
@@ -2658,3 +2720,489 @@ pub async fn remove_entity_from_collection(
     expire_user_collections_list_cache(user_id, ss).await?;
     Ok(StringIdObject { id: collect.id })
 }
+
+pub async fn metadata_list(
+    user_id: &String,
+    input: MetadataListInput,
+    ss: &Arc<SupportingService>,
+) -> Result<SearchResults<String>> {
+    let preferences = user_by_id(user_id, ss).await?.preferences;
+
+    let avg_rating_col = "user_average_rating";
+    let cloned_user_id_1 = user_id.clone();
+    let cloned_user_id_2 = user_id.clone();
+
+    let order_by = input
+        .sort
+        .clone()
+        .map(|a| graphql_to_db_order(a.order))
+        .unwrap_or(Order::Asc);
+    let review_scale = match preferences.general.review_scale {
+        UserReviewScale::OutOfFive => 20,
+        UserReviewScale::OutOfHundred | UserReviewScale::ThreePointSmiley => 1,
+    };
+    let take = input
+        .search
+        .clone()
+        .and_then(|s| s.take)
+        .unwrap_or(PAGE_SIZE as u64);
+    let page: u64 = input
+        .search
+        .clone()
+        .and_then(|s| s.page)
+        .unwrap_or(1)
+        .try_into()
+        .unwrap();
+    let paginator = Metadata::find()
+        .select_only()
+        .column(metadata::Column::Id)
+        .expr_as(
+            Func::round_with_precision(
+                Func::avg(
+                    Expr::col((AliasedReview::Table, AliasedReview::Rating)).div(review_scale),
+                ),
+                review_scale,
+            ),
+            avg_rating_col,
+        )
+        .group_by(metadata::Column::Id)
+        .group_by(user_to_entity::Column::MediaReason)
+        .filter(user_to_entity::Column::UserId.eq(user_id))
+        .apply_if(input.lot, |query, v| {
+            query.filter(metadata::Column::Lot.eq(v))
+        })
+        .inner_join(UserToEntity)
+        .join(
+            JoinType::LeftJoin,
+            metadata::Relation::Review
+                .def()
+                .on_condition(move |_left, right| {
+                    Condition::all().add(
+                        Expr::col((right, review::Column::UserId)).eq(cloned_user_id_1.clone()),
+                    )
+                }),
+        )
+        .join(
+            JoinType::LeftJoin,
+            metadata::Relation::Seen
+                .def()
+                .on_condition(move |_left, right| {
+                    Condition::all()
+                        .add(Expr::col((right, seen::Column::UserId)).eq(cloned_user_id_2.clone()))
+                }),
+        )
+        .apply_if(input.search.and_then(|s| s.query), |query, v| {
+            query.filter(
+                Condition::any()
+                    .add(Expr::col(metadata::Column::Title).ilike(ilike_sql(&v)))
+                    .add(Expr::col(metadata::Column::Description).ilike(ilike_sql(&v))),
+            )
+        })
+        .apply_if(
+            input.filter.clone().and_then(|f| f.collections),
+            |query, v| {
+                apply_collection_filter(
+                    query,
+                    Some(v),
+                    input.invert_collection,
+                    metadata::Column::Id,
+                    collection_to_entity::Column::MetadataId,
+                )
+            },
+        )
+        .apply_if(input.filter.and_then(|f| f.general), |query, v| match v {
+            MediaGeneralFilter::All => query.filter(metadata::Column::Id.is_not_null()),
+            MediaGeneralFilter::Rated => query.filter(review::Column::Id.is_not_null()),
+            MediaGeneralFilter::Unrated => query.filter(review::Column::Id.is_null()),
+            MediaGeneralFilter::Unfinished => query.filter(
+                Expr::expr(
+                    Expr::val(UserToMediaReason::Finished.to_string())
+                        .eq(PgFunc::any(Expr::col(user_to_entity::Column::MediaReason))),
+                )
+                .not(),
+            ),
+            s => query.filter(seen::Column::State.eq(match s {
+                MediaGeneralFilter::Dropped => SeenState::Dropped,
+                MediaGeneralFilter::OnAHold => SeenState::OnAHold,
+                _ => unreachable!(),
+            })),
+        })
+        .apply_if(input.sort.map(|s| s.by), |query, v| match v {
+            MediaSortBy::LastUpdated => query
+                .order_by(user_to_entity::Column::LastUpdatedOn, order_by)
+                .group_by(user_to_entity::Column::LastUpdatedOn),
+            MediaSortBy::Title => query.order_by(metadata::Column::Title, order_by),
+            MediaSortBy::ReleaseDate => query.order_by_with_nulls(
+                metadata::Column::PublishYear,
+                order_by,
+                NullOrdering::Last,
+            ),
+            MediaSortBy::LastSeen => query.order_by_with_nulls(
+                seen::Column::FinishedOn.max(),
+                order_by,
+                NullOrdering::Last,
+            ),
+            MediaSortBy::UserRating => query.order_by_with_nulls(
+                Expr::col(Alias::new(avg_rating_col)),
+                order_by,
+                NullOrdering::Last,
+            ),
+            MediaSortBy::ProviderRating => query.order_by_with_nulls(
+                metadata::Column::ProviderRating,
+                order_by,
+                NullOrdering::Last,
+            ),
+        })
+        .into_tuple::<String>()
+        .paginate(&ss.db, take);
+    let ItemsAndPagesNumber {
+        number_of_items,
+        number_of_pages,
+    } = paginator.num_items_and_pages().await?;
+    let mut items = vec![];
+    for c in paginator.fetch_page(page - 1).await? {
+        items.push(c);
+    }
+    Ok(SearchResults {
+        details: SearchDetails {
+            total: number_of_items.try_into().unwrap(),
+            next_page: if page < number_of_pages {
+                Some((page + 1).try_into().unwrap())
+            } else {
+                None
+            },
+        },
+        items,
+    })
+}
+
+pub async fn metadata_groups_list(
+    user_id: &String,
+    ss: &Arc<SupportingService>,
+    input: MetadataGroupsListInput,
+) -> Result<SearchResults<String>> {
+    let page: u64 = input
+        .search
+        .clone()
+        .and_then(|f| f.page)
+        .unwrap_or(1)
+        .try_into()
+        .unwrap();
+    let alias = "parts";
+    let media_items_col = Expr::col(Alias::new(alias));
+    let (order_by, sort_order) = match input.sort {
+        None => (media_items_col, Order::Desc),
+        Some(ord) => (
+            match ord.by {
+                PersonAndMetadataGroupsSortBy::Name => Expr::col(metadata_group::Column::Title),
+                PersonAndMetadataGroupsSortBy::MediaItems => media_items_col,
+            },
+            graphql_to_db_order(ord.order),
+        ),
+    };
+    let take = input
+        .search
+        .clone()
+        .and_then(|s| s.take)
+        .unwrap_or(PAGE_SIZE as u64);
+    let paginator = MetadataGroup::find()
+        .select_only()
+        .column(metadata_group::Column::Id)
+        .group_by(metadata_group::Column::Id)
+        .inner_join(UserToEntity)
+        .filter(user_to_entity::Column::UserId.eq(user_id))
+        .filter(metadata_group::Column::Id.is_not_null())
+        .apply_if(input.search.and_then(|f| f.query), |query, v| {
+            query.filter(
+                Condition::all().add(Expr::col(metadata_group::Column::Title).ilike(ilike_sql(&v))),
+            )
+        })
+        .apply_if(
+            input.filter.clone().and_then(|f| f.collections),
+            |query, v| {
+                apply_collection_filter(
+                    query,
+                    Some(v),
+                    input.invert_collection,
+                    metadata_group::Column::Id,
+                    collection_to_entity::Column::MetadataGroupId,
+                )
+            },
+        )
+        .order_by(order_by, sort_order)
+        .into_tuple::<String>()
+        .paginate(&ss.db, take);
+    let ItemsAndPagesNumber {
+        number_of_items,
+        number_of_pages,
+    } = paginator.num_items_and_pages().await?;
+    let mut items = vec![];
+    for c in paginator.fetch_page(page - 1).await? {
+        items.push(c);
+    }
+    Ok(SearchResults {
+        details: SearchDetails {
+            total: number_of_items.try_into().unwrap(),
+            next_page: if page < number_of_pages {
+                Some((page + 1).try_into().unwrap())
+            } else {
+                None
+            },
+        },
+        items,
+    })
+}
+
+pub async fn people_list(
+    user_id: &String,
+    input: PeopleListInput,
+    ss: &Arc<SupportingService>,
+) -> Result<SearchResults<String>> {
+    let page: u64 = input
+        .search
+        .clone()
+        .and_then(|f| f.page)
+        .unwrap_or(1)
+        .try_into()
+        .unwrap();
+    let alias = "media_count";
+    let media_items_col = Expr::col(Alias::new(alias));
+    let (order_by, sort_order) = match input.sort {
+        None => (media_items_col, Order::Desc),
+        Some(ord) => (
+            match ord.by {
+                PersonAndMetadataGroupsSortBy::Name => Expr::col(person::Column::Name),
+                PersonAndMetadataGroupsSortBy::MediaItems => media_items_col,
+            },
+            graphql_to_db_order(ord.order),
+        ),
+    };
+    let take = input
+        .search
+        .clone()
+        .and_then(|s| s.take)
+        .unwrap_or(PAGE_SIZE as u64);
+    let creators_paginator = Person::find()
+        .apply_if(input.search.clone().and_then(|s| s.query), |query, v| {
+            query.filter(Condition::all().add(Expr::col(person::Column::Name).ilike(ilike_sql(&v))))
+        })
+        .apply_if(
+            input.filter.clone().and_then(|f| f.collections),
+            |query, v| {
+                apply_collection_filter(
+                    query,
+                    Some(v),
+                    input.invert_collection,
+                    person::Column::Id,
+                    collection_to_entity::Column::PersonId,
+                )
+            },
+        )
+        .column_as(
+            Expr::expr(Func::count(Expr::col((
+                Alias::new("metadata_to_person"),
+                metadata_to_person::Column::MetadataId,
+            )))),
+            alias,
+        )
+        .filter(user_to_entity::Column::UserId.eq(user_id))
+        .left_join(MetadataToPerson)
+        .inner_join(UserToEntity)
+        .group_by(person::Column::Id)
+        .group_by(person::Column::Name)
+        .order_by(order_by, sort_order)
+        .into_tuple::<String>()
+        .paginate(&ss.db, take);
+    let ItemsAndPagesNumber {
+        number_of_items,
+        number_of_pages,
+    } = creators_paginator.num_items_and_pages().await?;
+    let mut creators = vec![];
+    for cr in creators_paginator.fetch_page(page - 1).await? {
+        creators.push(cr);
+    }
+    Ok(SearchResults {
+        details: SearchDetails {
+            total: number_of_items.try_into().unwrap(),
+            next_page: if page < number_of_pages {
+                Some((page + 1).try_into().unwrap())
+            } else {
+                None
+            },
+        },
+        items: creators,
+    })
+}
+
+pub async fn user_workouts_list(
+    user_id: &String,
+    input: SearchInput,
+    ss: &Arc<SupportingService>,
+) -> Result<SearchResults<String>> {
+    let page = input.page.unwrap_or(1);
+    let take = input.take.unwrap_or(PAGE_SIZE as u64);
+    let paginator = Workout::find()
+        .select_only()
+        .column(workout::Column::Id)
+        .filter(workout::Column::UserId.eq(user_id))
+        .apply_if(input.query, |query, v| {
+            query.filter(Expr::col(workout::Column::Name).ilike(ilike_sql(&v)))
+        })
+        .order_by_desc(workout::Column::EndTime)
+        .into_tuple::<String>()
+        .paginate(&ss.db, take);
+    let ItemsAndPagesNumber {
+        number_of_items,
+        number_of_pages,
+    } = paginator.num_items_and_pages().await?;
+    let items = paginator.fetch_page((page - 1).try_into().unwrap()).await?;
+    Ok(SearchResults {
+        details: SearchDetails {
+            total: number_of_items.try_into().unwrap(),
+            next_page: if page < number_of_pages.try_into().unwrap() {
+                Some(page + 1)
+            } else {
+                None
+            },
+        },
+        items,
+    })
+}
+
+pub async fn user_workout_templates_list(
+    user_id: &String,
+    input: SearchInput,
+    ss: &Arc<SupportingService>,
+) -> Result<SearchResults<String>> {
+    let page = input.page.unwrap_or(1);
+    let take = input.take.unwrap_or(PAGE_SIZE as u64);
+    let paginator = WorkoutTemplate::find()
+        .select_only()
+        .column(workout_template::Column::Id)
+        .filter(workout_template::Column::UserId.eq(user_id))
+        .apply_if(input.query, |query, v| {
+            query.filter(Expr::col(workout_template::Column::Name).ilike(ilike_sql(&v)))
+        })
+        .order_by_desc(workout_template::Column::CreatedOn)
+        .into_tuple::<String>()
+        .paginate(&ss.db, take);
+    let ItemsAndPagesNumber {
+        number_of_items,
+        number_of_pages,
+    } = paginator.num_items_and_pages().await?;
+    let items = paginator.fetch_page((page - 1).try_into().unwrap()).await?;
+    Ok(SearchResults {
+        details: SearchDetails {
+            total: number_of_items.try_into().unwrap(),
+            next_page: if page < number_of_pages.try_into().unwrap() {
+                Some(page + 1)
+            } else {
+                None
+            },
+        },
+        items,
+    })
+}
+
+pub async fn exercises_list(
+    user_id: &String,
+    input: ExercisesListInput,
+    ss: &Arc<SupportingService>,
+) -> Result<SearchResults<String>> {
+    let user_id = user_id.to_owned();
+    let take = input.search.take.unwrap_or(PAGE_SIZE as u64);
+    let page = input.search.page.unwrap_or(1);
+    let ex = Alias::new("exercise");
+    let etu = Alias::new("user_to_entity");
+    let order_by_col = match input.sort_by {
+        None => Expr::col((ex, exercise::Column::Id)),
+        Some(sb) => match sb {
+            // DEV: This is just a small hack to reduce duplicated code. We
+            // are ordering by name for the other `sort_by` anyway.
+            ExerciseSortBy::Name => Expr::val("1"),
+            ExerciseSortBy::TimesPerformed => Expr::expr(Func::coalesce([
+                Expr::col((
+                    etu.clone(),
+                    user_to_entity::Column::ExerciseNumTimesInteracted,
+                ))
+                .into(),
+                Expr::val(0).into(),
+            ])),
+            ExerciseSortBy::LastPerformed => Expr::expr(Func::coalesce([
+                Expr::col((etu.clone(), user_to_entity::Column::LastUpdatedOn)).into(),
+                // DEV: For some reason this does not work without explicit casting on postgres
+                Func::cast_as(Expr::val("1900-01-01"), Alias::new("timestamptz")).into(),
+            ])),
+        },
+    };
+    let paginator = Exercise::find()
+        .select_only()
+        .column(exercise::Column::Id)
+        .filter(
+            exercise::Column::Source
+                .eq(ExerciseSource::Github)
+                .or(exercise::Column::CreatedByUserId.eq(&user_id)),
+        )
+        .apply_if(input.filter, |query, q| {
+            query
+                .apply_if(q.lot, |q, v| q.filter(exercise::Column::Lot.eq(v)))
+                .apply_if(q.muscle, |q, v| {
+                    q.filter(Expr::val(v).eq(PgFunc::any(Expr::col(exercise::Column::Muscles))))
+                })
+                .apply_if(q.level, |q, v| q.filter(exercise::Column::Level.eq(v)))
+                .apply_if(q.force, |q, v| q.filter(exercise::Column::Force.eq(v)))
+                .apply_if(q.mechanic, |q, v| {
+                    q.filter(exercise::Column::Mechanic.eq(v))
+                })
+                .apply_if(q.equipment, |q, v| {
+                    q.filter(exercise::Column::Equipment.eq(v))
+                })
+                .apply_if(q.collection, |q, v| {
+                    q.left_join(CollectionToEntity)
+                        .filter(collection_to_entity::Column::CollectionId.eq(v))
+                })
+        })
+        .apply_if(input.search.query, |query, v| {
+            query.filter(
+                Condition::any()
+                    .add(
+                        Expr::col((AliasedExercise::Table, AliasedExercise::Id))
+                            .ilike(ilike_sql(&v)),
+                    )
+                    .add(Expr::col(exercise::Column::Name).ilike(slugify(v))),
+            )
+        })
+        .join(
+            JoinType::LeftJoin,
+            user_to_entity::Relation::Exercise
+                .def()
+                .rev()
+                .on_condition(move |_left, right| {
+                    Condition::all()
+                        .add(Expr::col((right, user_to_entity::Column::UserId)).eq(&user_id))
+                }),
+        )
+        .order_by_desc(order_by_col)
+        .order_by_asc(exercise::Column::Id)
+        .into_tuple::<String>()
+        .paginate(&ss.db, take);
+    let ItemsAndPagesNumber {
+        number_of_items,
+        number_of_pages,
+    } = paginator.num_items_and_pages().await?;
+    let mut items = vec![];
+    for ex in paginator.fetch_page((page - 1).try_into().unwrap()).await? {
+        items.push(ex);
+    }
+    Ok(SearchResults {
+        details: SearchDetails {
+            total: number_of_items.try_into().unwrap(),
+            next_page: if page < number_of_pages.try_into().unwrap() {
+                Some(page + 1)
+            } else {
+                None
+            },
+        },
+        items,
+    })
+}
diff --git a/docs/content/importing.md b/docs/content/importing.md
index 7186f107b9..02bba4949a 100644
--- a/docs/content/importing.md
+++ b/docs/content/importing.md
@@ -11,6 +11,8 @@ You can see the reports under "Import History" of the imports page.
 
 - Imports are very difficult to have 100% success rate. Though we try our best,
   you might have to manually import some data from your previous provider.
+- Imports might take a long time since Ryot needs to fetch all metadata from the sources
+  before it can start importing progress. Estimated finish time is displayed in the UI.
 - I recommend turning on debug logging for the duration of the import using the
   `RUST_LOG=ryot=debug` environment variable. This will help you help you see import
   progress in the docker logs.
diff --git a/libs/generated/src/graphql/backend/gql.ts b/libs/generated/src/graphql/backend/gql.ts
index db3d9ab877..69fbfbf03c 100644
--- a/libs/generated/src/graphql/backend/gql.ts
+++ b/libs/generated/src/graphql/backend/gql.ts
@@ -22,7 +22,7 @@ const documents = {
     "query UserExerciseDetails($exerciseId: String!) {\n  userExerciseDetails(exerciseId: $exerciseId) {\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n    history {\n      idx\n      workoutId\n      workoutEndOn\n      bestSet {\n        ...WorkoutSetRecordPart\n      }\n    }\n    details {\n      exerciseId\n      createdOn\n      lastUpdatedOn\n      exerciseNumTimesInteracted\n      exerciseExtraInformation {\n        settings {\n          excludeFromAnalytics\n          setRestTimers {\n            ...SetRestTimersPart\n          }\n        }\n        lifetimeStats {\n          weight\n          reps\n          distance\n          duration\n          personalBestsAchieved\n        }\n        personalBests {\n          lot\n          sets {\n            setIdx\n            workoutId\n            exerciseIdx\n          }\n        }\n      }\n    }\n  }\n}": types.UserExerciseDetailsDocument,
     "query UserMeasurementsList($input: UserMeasurementsListInput!) {\n  userMeasurementsList(input: $input) {\n    timestamp\n    name\n    comment\n    stats {\n      weight\n      bodyMassIndex\n      totalBodyWater\n      muscle\n      leanBodyMass\n      bodyFat\n      boneMass\n      visceralFat\n      waistCircumference\n      waistToHeightRatio\n      hipCircumference\n      waistToHipRatio\n      chestCircumference\n      thighCircumference\n      bicepsCircumference\n      neckCircumference\n      bodyFatCaliper\n      chestSkinfold\n      abdominalSkinfold\n      thighSkinfold\n      basalMetabolicRate\n      totalDailyEnergyExpenditure\n      calories\n      custom\n    }\n  }\n}": types.UserMeasurementsListDocument,
     "query UserMetadataDetails($metadataId: String!) {\n  userMetadataDetails(metadataId: $metadataId) {\n    mediaReason\n    hasInteracted\n    averageRating\n    seenByAllCount\n    seenByUserCount\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    history {\n      ...SeenPart\n    }\n    nextEntry {\n      season\n      volume\n      episode\n      chapter\n    }\n    inProgress {\n      ...SeenPart\n    }\n    collections {\n      ...CollectionPart\n    }\n    showProgress {\n      timesSeen\n      seasonNumber\n      episodes {\n        episodeNumber\n        timesSeen\n      }\n    }\n    podcastProgress {\n      episodeNumber\n      timesSeen\n    }\n  }\n}": types.UserMetadataDetailsDocument,
-    "query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      lot\n      name\n      image\n      muscle\n      lastUpdatedOn\n      numTimesInteracted\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery 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}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\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  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    details {\n      id\n      lot\n      title\n      parts\n      source\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      title\n      image\n      publishYear\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\n  }\n}": types.GetOidcRedirectUrlDocument,
+    "query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    items\n    details {\n      total\n      nextPage\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    estimatedFinishTime\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery 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}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\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  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    details {\n      id\n      lot\n      title\n      parts\n      source\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      title\n      image\n      publishYear\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\n  }\n}": types.GetOidcRedirectUrlDocument,
     "fragment SeenPodcastExtraInformationPart on SeenPodcastExtraInformation {\n  episode\n}\n\nfragment SeenShowExtraInformationPart on SeenShowExtraInformation {\n  episode\n  season\n}\n\nfragment SeenAnimeExtraInformationPart on SeenAnimeExtraInformation {\n  episode\n}\n\nfragment SeenMangaExtraInformationPart on SeenMangaExtraInformation {\n  volume\n  chapter\n}\n\nfragment CalendarEventPart on GraphqlCalendarEvent {\n  date\n  metadataId\n  metadataLot\n  episodeName\n  metadataTitle\n  metadataImage\n  calendarEventId\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n}\n\nfragment SeenPart on Seen {\n  id\n  state\n  progress\n  reviewId\n  startedOn\n  finishedOn\n  lastUpdatedOn\n  manualTimeSpent\n  numTimesUpdated\n  providerWatchedOn\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}\n\nfragment MetadataSearchItemPart on MetadataSearchItem {\n  title\n  image\n  identifier\n  publishYear\n}\n\nfragment WorkoutOrExerciseTotalsPart on WorkoutOrExerciseTotals {\n  reps\n  weight\n  distance\n  duration\n  restTime\n  personalBestsAchieved\n}\n\nfragment EntityAssetsPart on EntityAssets {\n  images\n  videos\n}\n\nfragment WorkoutSetStatisticPart on WorkoutSetStatistic {\n  reps\n  pace\n  oneRm\n  weight\n  volume\n  duration\n  distance\n}\n\nfragment WorkoutSetRecordPart on WorkoutSetRecord {\n  lot\n  personalBests\n  statistic {\n    ...WorkoutSetStatisticPart\n  }\n}\n\nfragment WorkoutSummaryPart on WorkoutSummary {\n  total {\n    ...WorkoutOrExerciseTotalsPart\n  }\n  exercises {\n    id\n    lot\n    numSets\n    bestSet {\n      ...WorkoutSetRecordPart\n    }\n  }\n  focused {\n    lots {\n      lot\n      exercises\n    }\n    levels {\n      level\n      exercises\n    }\n    forces {\n      force\n      exercises\n    }\n    muscles {\n      muscle\n      exercises\n    }\n    equipments {\n      equipment\n      exercises\n    }\n  }\n}\n\nfragment CollectionPart on Collection {\n  id\n  name\n  userId\n}\n\nfragment ReviewItemPart on ReviewItem {\n  id\n  rating\n  postedOn\n  isSpoiler\n  visibility\n  textOriginal\n  textRendered\n  seenItemsAssociatedWith\n  postedBy {\n    id\n    name\n  }\n  comments {\n    id\n    text\n    likedBy\n    createdOn\n    user {\n      id\n      name\n    }\n  }\n  showExtraInformation {\n    season\n    episode\n  }\n  podcastExtraInformation {\n    episode\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}\n\nfragment WorkoutInformationPart on WorkoutInformation {\n  comment\n  assets {\n    ...EntityAssetsPart\n  }\n  supersets {\n    color\n    exercises\n  }\n  exercises {\n    id\n    lot\n    notes\n    total {\n      ...WorkoutOrExerciseTotalsPart\n    }\n    assets {\n      ...EntityAssetsPart\n    }\n    sets {\n      lot\n      rpe\n      note\n      restTime\n      confirmedAt\n      personalBests\n      statistic {\n        ...WorkoutSetStatisticPart\n      }\n    }\n  }\n}\n\nfragment SetRestTimersPart on SetRestTimersSettings {\n  drop\n  warmup\n  normal\n  failure\n}\n\nfragment PersonDetailsGroupedByRolePart on PersonDetailsGroupedByRole {\n  name\n  count\n  items {\n    entityId\n    character\n  }\n}": types.SeenPodcastExtraInformationPartFragmentDoc,
 };
 
@@ -75,7 +75,7 @@ export function graphql(source: "query UserMetadataDetails($metadataId: String!)
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
-export function graphql(source: "query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      lot\n      name\n      image\n      muscle\n      lastUpdatedOn\n      numTimesInteracted\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery 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}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\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  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    details {\n      id\n      lot\n      title\n      parts\n      source\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      title\n      image\n      publishYear\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\n  }\n}"): (typeof documents)["query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      lot\n      name\n      image\n      muscle\n      lastUpdatedOn\n      numTimesInteracted\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery 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}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\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  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    details {\n      id\n      lot\n      title\n      parts\n      source\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      title\n      image\n      publishYear\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\n  }\n}"];
+export function graphql(source: "query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    items\n    details {\n      total\n      nextPage\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    estimatedFinishTime\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery 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}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\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  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    details {\n      id\n      lot\n      title\n      parts\n      source\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      title\n      image\n      publishYear\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\n  }\n}"): (typeof documents)["query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    items\n    details {\n      total\n      nextPage\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    estimatedFinishTime\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery 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}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\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  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    details {\n      id\n      lot\n      title\n      parts\n      source\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      title\n      image\n      publishYear\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\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 c919269ed7..2fa3ba4b41 100644
--- a/libs/generated/src/graphql/backend/graphql.ts
+++ b/libs/generated/src/graphql/backend/graphql.ts
@@ -165,7 +165,6 @@ export type CollectionContentsInput = {
   filter?: InputMaybe<CollectionContentsFilter>;
   search?: InputMaybe<SearchInput>;
   sort?: InputMaybe<CollectionContentsSortInput>;
-  take?: InputMaybe<Scalars['Int']['input']>;
 };
 
 export enum CollectionContentsSortBy {
@@ -580,21 +579,6 @@ export type ExerciseListFilter = {
   type?: InputMaybe<ExerciseLot>;
 };
 
-export type ExerciseListItem = {
-  id: Scalars['String']['output'];
-  image?: Maybe<Scalars['String']['output']>;
-  lastUpdatedOn?: Maybe<Scalars['DateTime']['output']>;
-  lot: ExerciseLot;
-  muscle?: Maybe<ExerciseMuscle>;
-  name: Scalars['String']['output'];
-  numTimesInteracted?: Maybe<Scalars['Int']['output']>;
-};
-
-export type ExerciseListResults = {
-  details: SearchDetails;
-  items: Array<ExerciseListItem>;
-};
-
 /** The different types of exercises that can be done. */
 export enum ExerciseLot {
   DistanceAndDuration = 'DISTANCE_AND_DURATION',
@@ -823,16 +807,14 @@ export type ImportDetails = {
 
 /** The various steps in which media importing can fail */
 export enum ImportFailStep {
+  /** Failed to save an entity/review/progress item */
+  DatabaseCommit = 'DATABASE_COMMIT',
   /** Failed to transform the data into the required format */
   InputTransformation = 'INPUT_TRANSFORMATION',
-  /** Failed to get details from the source itself (for eg: MediaTracker, Goodreads etc.) */
+  /** Failed to get details from the source (eg: MediaTracker, Goodreads etc.) */
   ItemDetailsFromSource = 'ITEM_DETAILS_FROM_SOURCE',
-  /** Failed to get metadata from the provider (for eg: Openlibrary, IGDB etc.) */
-  MediaDetailsFromProvider = 'MEDIA_DETAILS_FROM_PROVIDER',
-  /** Failed to save a review/rating item */
-  ReviewConversion = 'REVIEW_CONVERSION',
-  /** Failed to save a seen history item */
-  SeenHistoryConversion = 'SEEN_HISTORY_CONVERSION'
+  /** Failed to get metadata from the provider (eg: Openlibrary, IGDB etc.) */
+  MediaDetailsFromProvider = 'MEDIA_DETAILS_FROM_PROVIDER'
 }
 
 export type ImportFailedItem = {
@@ -854,6 +836,7 @@ export type ImportOrExportItemReviewComment = {
 
 export type ImportReport = {
   details?: Maybe<ImportResultResponse>;
+  estimatedFinishTime: Scalars['DateTime']['output'];
   finishedOn?: Maybe<Scalars['DateTime']['output']>;
   id: Scalars['String']['output'];
   progress?: Maybe<Scalars['Decimal']['output']>;
@@ -1095,7 +1078,6 @@ export type MetadataGroupsListInput = {
   invertCollection?: InputMaybe<Scalars['Boolean']['input']>;
   search?: InputMaybe<SearchInput>;
   sort?: InputMaybe<PersonSortInput>;
-  take?: InputMaybe<Scalars['Int']['input']>;
 };
 
 export type MetadataListInput = {
@@ -1104,7 +1086,6 @@ export type MetadataListInput = {
   lot?: InputMaybe<MediaLot>;
   search?: InputMaybe<SearchInput>;
   sort?: InputMaybe<MediaSortInput>;
-  take?: InputMaybe<Scalars['Int']['input']>;
 };
 
 export type MetadataLotSourceMappings = {
@@ -1584,7 +1565,6 @@ export type PeopleListInput = {
   invertCollection?: InputMaybe<Scalars['Boolean']['input']>;
   search?: InputMaybe<SearchInput>;
   sort?: InputMaybe<PersonSortInput>;
-  take?: InputMaybe<Scalars['Int']['input']>;
 };
 
 export type PeopleSearchInput = {
@@ -1754,7 +1734,7 @@ export type QueryRoot = {
   /** Get details about an exercise. */
   exerciseDetails: Exercise;
   /** Get a paginated list of exercises in the database. */
-  exercisesList: ExerciseListResults;
+  exercisesList: IdResults;
   /** Get details about a genre present in the database. */
   genreDetails: GenreDetails;
   /** Get paginated list of genres. */
@@ -2035,6 +2015,7 @@ export type SearchDetails = {
 export type SearchInput = {
   page?: InputMaybe<Scalars['Int']['input']>;
   query?: InputMaybe<Scalars['String']['input']>;
+  take?: InputMaybe<Scalars['Int']['input']>;
 };
 
 export type Seen = {
@@ -3560,12 +3541,12 @@ export type ExercisesListQueryVariables = Exact<{
 }>;
 
 
-export type ExercisesListQuery = { exercisesList: { details: { total: number, nextPage?: number | null }, items: Array<{ id: string, lot: ExerciseLot, name: string, image?: string | null, muscle?: ExerciseMuscle | null, lastUpdatedOn?: string | null, numTimesInteracted?: number | null }> } };
+export type ExercisesListQuery = { exercisesList: { items: Array<string>, details: { total: number, nextPage?: number | null } } };
 
 export type ImportReportsQueryVariables = Exact<{ [key: string]: never; }>;
 
 
-export type ImportReportsQuery = { importReports: Array<{ id: string, source: ImportSource, progress?: string | null, startedOn: string, finishedOn?: string | null, wasSuccess?: boolean | null, details?: { import: { total: number }, failedItems: Array<{ lot?: MediaLot | null, step: ImportFailStep, error?: string | null, identifier: string }> } | null }> };
+export type ImportReportsQuery = { importReports: Array<{ id: string, source: ImportSource, progress?: string | null, startedOn: string, finishedOn?: string | null, wasSuccess?: boolean | null, estimatedFinishTime: string, details?: { import: { total: number }, failedItems: Array<{ lot?: MediaLot | null, step: ImportFailStep, error?: string | null, identifier: string }> } | null }> };
 
 export type GenresListQueryVariables = Exact<{
   input: SearchInput;
@@ -3804,8 +3785,8 @@ export const MetadataGroupsListDocument = {"kind":"Document","definitions":[{"ki
 export const PeopleListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PeopleList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PeopleListInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"peopleList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"}}]}}]}}]} as unknown as DocumentNode<PeopleListQuery, PeopleListQueryVariables>;
 export const UserAccessLinksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserAccessLinks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userAccessLinks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isDemo"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"expiresOn"}},{"kind":"Field","name":{"kind":"Name","value":"timesUsed"}},{"kind":"Field","name":{"kind":"Name","value":"isRevoked"}},{"kind":"Field","name":{"kind":"Name","value":"maximumUses"}},{"kind":"Field","name":{"kind":"Name","value":"isAccountDefault"}},{"kind":"Field","name":{"kind":"Name","value":"isMutationAllowed"}}]}}]}}]} as unknown as DocumentNode<UserAccessLinksQuery, UserAccessLinksQueryVariables>;
 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":"name"}},{"kind":"Field","name":{"kind":"Name","value":"level"}},{"kind":"Field","name":{"kind":"Name","value":"force"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"muscles"}},{"kind":"Field","name":{"kind":"Name","value":"mechanic"}},{"kind":"Field","name":{"kind":"Name","value":"equipment"}},{"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<ExerciseDetailsQuery, ExerciseDetailsQueryVariables>;
-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":"name"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedOn"}},{"kind":"Field","name":{"kind":"Name","value":"numTimesInteracted"}}]}}]}}]}}]} as unknown as DocumentNode<ExercisesListQuery, ExercisesListQueryVariables>;
-export const ImportReportsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ImportReports"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importReports"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"progress"}},{"kind":"Field","name":{"kind":"Name","value":"startedOn"}},{"kind":"Field","name":{"kind":"Name","value":"finishedOn"}},{"kind":"Field","name":{"kind":"Name","value":"wasSuccess"}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"import"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}}]}},{"kind":"Field","name":{"kind":"Name","value":"failedItems"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"step"}},{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}}]}}]}}]}}]}}]} as unknown as DocumentNode<ImportReportsQuery, ImportReportsQueryVariables>;
+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":"items"}},{"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"}}]}}]}}]}}]} as unknown as DocumentNode<ExercisesListQuery, ExercisesListQueryVariables>;
+export const ImportReportsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ImportReports"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importReports"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"progress"}},{"kind":"Field","name":{"kind":"Name","value":"startedOn"}},{"kind":"Field","name":{"kind":"Name","value":"finishedOn"}},{"kind":"Field","name":{"kind":"Name","value":"wasSuccess"}},{"kind":"Field","name":{"kind":"Name","value":"estimatedFinishTime"}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"import"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}}]}},{"kind":"Field","name":{"kind":"Name","value":"failedItems"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"step"}},{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}}]}}]}}]}}]}}]} as unknown as DocumentNode<ImportReportsQuery, ImportReportsQueryVariables>;
 export const GenresListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GenresList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SearchInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"genresList"},"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":"name"}},{"kind":"Field","name":{"kind":"Name","value":"numItems"}}]}}]}}]}}]} as unknown as DocumentNode<GenresListQuery, GenresListQueryVariables>;
 export const GenreDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GenreDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GenreDetailsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"genreDetails"},"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":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"numItems"}}]}},{"kind":"Field","name":{"kind":"Name","value":"contents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"}}]}}]}}]}}]} as unknown as DocumentNode<GenreDetailsQuery, GenreDetailsQueryVariables>;
 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":"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":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"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<CollectionContentsQuery, CollectionContentsQueryVariables>;
diff --git a/libs/generated/src/graphql/backend/types.generated.ts b/libs/generated/src/graphql/backend/types.generated.ts
index 92765f716d..b1b3f4824e 100644
--- a/libs/generated/src/graphql/backend/types.generated.ts
+++ b/libs/generated/src/graphql/backend/types.generated.ts
@@ -148,7 +148,6 @@ export type CollectionContentsInput = {
   filter?: InputMaybe<CollectionContentsFilter>;
   search?: InputMaybe<SearchInput>;
   sort?: InputMaybe<CollectionContentsSortInput>;
-  take?: InputMaybe<Scalars['Int']['input']>;
 };
 
 export enum CollectionContentsSortBy {
@@ -576,23 +575,6 @@ export type ExerciseListFilter = {
   type?: InputMaybe<ExerciseLot>;
 };
 
-export type ExerciseListItem = {
-  __typename?: 'ExerciseListItem';
-  id: Scalars['String']['output'];
-  image?: Maybe<Scalars['String']['output']>;
-  lastUpdatedOn?: Maybe<Scalars['DateTime']['output']>;
-  lot: ExerciseLot;
-  muscle?: Maybe<ExerciseMuscle>;
-  name: Scalars['String']['output'];
-  numTimesInteracted?: Maybe<Scalars['Int']['output']>;
-};
-
-export type ExerciseListResults = {
-  __typename?: 'ExerciseListResults';
-  details: SearchDetails;
-  items: Array<ExerciseListItem>;
-};
-
 /** The different types of exercises that can be done. */
 export enum ExerciseLot {
   DistanceAndDuration = 'DISTANCE_AND_DURATION',
@@ -842,16 +824,14 @@ export type ImportDetails = {
 
 /** The various steps in which media importing can fail */
 export enum ImportFailStep {
+  /** Failed to save an entity/review/progress item */
+  DatabaseCommit = 'DATABASE_COMMIT',
   /** Failed to transform the data into the required format */
   InputTransformation = 'INPUT_TRANSFORMATION',
-  /** Failed to get details from the source itself (for eg: MediaTracker, Goodreads etc.) */
+  /** Failed to get details from the source (eg: MediaTracker, Goodreads etc.) */
   ItemDetailsFromSource = 'ITEM_DETAILS_FROM_SOURCE',
-  /** Failed to get metadata from the provider (for eg: Openlibrary, IGDB etc.) */
-  MediaDetailsFromProvider = 'MEDIA_DETAILS_FROM_PROVIDER',
-  /** Failed to save a review/rating item */
-  ReviewConversion = 'REVIEW_CONVERSION',
-  /** Failed to save a seen history item */
-  SeenHistoryConversion = 'SEEN_HISTORY_CONVERSION'
+  /** Failed to get metadata from the provider (eg: Openlibrary, IGDB etc.) */
+  MediaDetailsFromProvider = 'MEDIA_DETAILS_FROM_PROVIDER'
 }
 
 export type ImportFailedItem = {
@@ -876,6 +856,7 @@ export type ImportOrExportItemReviewComment = {
 export type ImportReport = {
   __typename?: 'ImportReport';
   details?: Maybe<ImportResultResponse>;
+  estimatedFinishTime: Scalars['DateTime']['output'];
   finishedOn?: Maybe<Scalars['DateTime']['output']>;
   id: Scalars['String']['output'];
   progress?: Maybe<Scalars['Decimal']['output']>;
@@ -1129,7 +1110,6 @@ export type MetadataGroupsListInput = {
   invertCollection?: InputMaybe<Scalars['Boolean']['input']>;
   search?: InputMaybe<SearchInput>;
   sort?: InputMaybe<PersonSortInput>;
-  take?: InputMaybe<Scalars['Int']['input']>;
 };
 
 export type MetadataListInput = {
@@ -1138,7 +1118,6 @@ export type MetadataListInput = {
   lot?: InputMaybe<MediaLot>;
   search?: InputMaybe<SearchInput>;
   sort?: InputMaybe<MediaSortInput>;
-  take?: InputMaybe<Scalars['Int']['input']>;
 };
 
 export type MetadataLotSourceMappings = {
@@ -1627,7 +1606,6 @@ export type PeopleListInput = {
   invertCollection?: InputMaybe<Scalars['Boolean']['input']>;
   search?: InputMaybe<SearchInput>;
   sort?: InputMaybe<PersonSortInput>;
-  take?: InputMaybe<Scalars['Int']['input']>;
 };
 
 export type PeopleSearchInput = {
@@ -1810,7 +1788,7 @@ export type QueryRoot = {
   /** Get details about an exercise. */
   exerciseDetails: Exercise;
   /** Get a paginated list of exercises in the database. */
-  exercisesList: ExerciseListResults;
+  exercisesList: IdResults;
   /** Get details about a genre present in the database. */
   genreDetails: GenreDetails;
   /** Get paginated list of genres. */
@@ -2094,6 +2072,7 @@ export type SearchDetails = {
 export type SearchInput = {
   page?: InputMaybe<Scalars['Int']['input']>;
   query?: InputMaybe<Scalars['String']['input']>;
+  take?: InputMaybe<Scalars['Int']['input']>;
 };
 
 export type Seen = {
diff --git a/libs/graphql/src/backend/queries/combined.gql b/libs/graphql/src/backend/queries/combined.gql
index 98bbd9eb9f..90513594d4 100644
--- a/libs/graphql/src/backend/queries/combined.gql
+++ b/libs/graphql/src/backend/queries/combined.gql
@@ -169,19 +169,11 @@ query ExerciseDetails($exerciseId: String!) {
 
 query ExercisesList($input: ExercisesListInput!) {
 	exercisesList(input: $input) {
+		items
 		details {
 			total
 			nextPage
 		}
-		items {
-			id
-			lot
-			name
-			image
-			muscle
-			lastUpdatedOn
-			numTimesInteracted
-		}
 	}
 }
 
@@ -193,6 +185,7 @@ query ImportReports {
 		startedOn
 		finishedOn
 		wasSuccess
+		estimatedFinishTime
 		details {
 			import {
 				total


Test patch: