Repository: IgnisDa/ryot (language: rust)

Issue:
Allow associating person with metadata groups
This will be useful for tracking album releases for person.


Discussion / hints:

Fix patch:
diff --git a/apps/frontend/app/components/media.tsx b/apps/frontend/app/components/media.tsx
index 636dc21919..4f9296ac84 100644
--- a/apps/frontend/app/components/media.tsx
+++ b/apps/frontend/app/components/media.tsx
@@ -2,6 +2,7 @@ import {
 	ActionIcon,
 	Anchor,
 	Avatar,
+	Box,
 	Group,
 	Loader,
 	Menu,
@@ -14,7 +15,6 @@ import { useInViewport } from "@mantine/hooks";
 import { Form, Link } from "@remix-run/react";
 import {
 	EntityLot,
-	MetadataGroupDetailsDocument,
 	PersonDetailsDocument,
 	SeenState,
 	UserMetadataGroupDetailsDocument,
@@ -42,6 +42,7 @@ import {
 } from "~/components/common";
 import {
 	clientGqlService,
+	getMetadataGroupDetailsQuery,
 	getPartialMetadataDetailsQuery,
 	openConfirmationModal,
 	queryFactory,
@@ -57,31 +58,32 @@ import {
 import { useMetadataProgressUpdate, useReviewEntity } from "~/lib/state/media";
 import classes from "~/styles/common.module.css";
 
-export const PartialMetadataDisplay = (props: {
-	metadataId: string;
-	extraText?: string;
-}) => {
-	const { data: metadataDetails } = useQuery(
-		getPartialMetadataDetailsQuery(props.metadataId),
-	);
-	const { data: userMetadataDetails } = useUserMetadataDetails(
-		props.metadataId,
+const WrapperComponent = (props: { link?: string; children: ReactNode }) =>
+	props.link ? (
+		<Anchor component={Link} to={props.link}>
+			{props.children}
+		</Anchor>
+	) : (
+		<Box>{props.children}</Box>
 	);
 
+export const BaseEntityDisplay = (props: {
+	link?: string;
+	image?: string;
+	title?: string;
+	extraText?: string;
+	hasInteracted?: boolean;
+}) => {
 	return (
-		<Anchor
-			component={Link}
-			data-media-id={props.metadataId}
-			to={$path("/media/item/:id", { id: props.metadataId })}
-		>
+		<WrapperComponent link={props.link}>
 			<Avatar
-				imageProps={{ loading: "lazy" }}
-				radius="sm"
-				src={metadataDetails?.image}
-				h={100}
 				w={85}
+				h={100}
 				mx="auto"
-				name={metadataDetails?.title}
+				radius="sm"
+				src={props.image}
+				name={props.title}
+				imageProps={{ loading: "lazy" }}
 				styles={{ image: { objectPosition: "top" } }}
 			/>
 			<Text
@@ -89,11 +91,33 @@ export const PartialMetadataDisplay = (props: {
 				size="xs"
 				ta="center"
 				lineClamp={1}
-				c={userMetadataDetails?.hasInteracted ? "yellow" : "dimmed"}
+				c={props.hasInteracted ? "yellow" : "dimmed"}
 			>
-				{metadataDetails?.title} {props.extraText}
+				{props.title} {props.extraText}
 			</Text>
-		</Anchor>
+		</WrapperComponent>
+	);
+};
+
+export const PartialMetadataDisplay = (props: {
+	metadataId: string;
+	extraText?: string;
+}) => {
+	const { data: metadataDetails } = useQuery(
+		getPartialMetadataDetailsQuery(props.metadataId),
+	);
+	const { data: userMetadataDetails } = useUserMetadataDetails(
+		props.metadataId,
+	);
+
+	return (
+		<BaseEntityDisplay
+			extraText={props.extraText}
+			image={metadataDetails?.image || undefined}
+			title={metadataDetails?.title || undefined}
+			hasInteracted={userMetadataDetails?.hasInteracted}
+			link={$path("/media/item/:id", { id: props.metadataId })}
+		/>
 	);
 };
 
@@ -275,13 +299,7 @@ export const MetadataGroupDisplayItem = (props: {
 	const { ref, inViewport } = useInViewport();
 	const { data: metadataDetails, isLoading: isMetadataDetailsLoading } =
 		useQuery({
-			queryKey: queryFactory.media.metadataGroupDetails(props.metadataGroupId)
-				.queryKey,
-			queryFn: async () => {
-				return clientGqlService
-					.request(MetadataGroupDetailsDocument, props)
-					.then((data) => data.metadataGroupDetails);
-			},
+			...getMetadataGroupDetailsQuery(props.metadataGroupId),
 			enabled: inViewport,
 		});
 	const { data: userMetadataGroupDetails } = useQuery({
@@ -348,6 +366,17 @@ export const PersonDisplayItem = (props: {
 		enabled: inViewport,
 	});
 
+	const metadataCount =
+		personDetails?.associatedMetadata.reduce(
+			(sum, content) => sum + content.items.length,
+			0,
+		) || 0;
+	const metadataGroupCount =
+		personDetails?.associatedMetadataGroups.reduce(
+			(sum, content) => sum + content.items.length,
+			0,
+		) || 0;
+
 	return (
 		<BaseMediaDisplayItem
 			innerRef={ref}
@@ -360,10 +389,10 @@ export const PersonDisplayItem = (props: {
 				id: props.personId,
 			})}
 			labels={{
+				right: props.rightLabel,
 				left: personDetails
-					? `${personDetails.contents.reduce((sum, content) => sum + content.items.length, 0)} items`
+					? `${metadataCount + metadataGroupCount} items`
 					: undefined,
-				right: props.rightLabel,
 			}}
 		/>
 	);
diff --git a/apps/frontend/app/lib/generals.tsx b/apps/frontend/app/lib/generals.tsx
index 135365904c..7de3deaf74 100644
--- a/apps/frontend/app/lib/generals.tsx
+++ b/apps/frontend/app/lib/generals.tsx
@@ -9,6 +9,7 @@ import {
 	MediaLot,
 	MediaSource,
 	MetadataDetailsDocument,
+	MetadataGroupDetailsDocument,
 	MetadataPartialDetailsDocument,
 	SetLot,
 	type UserAnalyticsQueryVariables,
@@ -443,6 +444,17 @@ export const getUserMetadataDetailsQuery = (metadataId?: string | null) =>
 			: skipToken,
 	});
 
+export const getMetadataGroupDetailsQuery = (metadataGroupId: string) =>
+	queryOptions({
+		queryKey: queryFactory.media.metadataGroupDetails(metadataGroupId).queryKey,
+		queryFn: metadataGroupId
+			? () =>
+					clientGqlService
+						.request(MetadataGroupDetailsDocument, { metadataGroupId })
+						.then((data) => data.metadataGroupDetails)
+			: skipToken,
+	});
+
 export const getTimeOfDay = (hours: number) => {
 	if (hours >= 5 && hours < 12) return "Morning";
 	if (hours >= 12 && hours < 17) return "Afternoon";
diff --git a/apps/frontend/app/root.tsx b/apps/frontend/app/root.tsx
index 455856bbba..6cedb493a2 100644
--- a/apps/frontend/app/root.tsx
+++ b/apps/frontend/app/root.tsx
@@ -117,7 +117,7 @@ export default function App() {
 					content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no, user-scalable=no, viewport-fit=cover"
 				/>
 				<link rel="manifest" href="/manifest.json" />
-				<link rel="apple-touch-icon" href={"/icon-192x192.png"} />
+				<link rel="apple-touch-icon" href="/icons/maskable_icon_x180.png" />
 				<Meta />
 				<Links />
 				<ColorSchemeScript forceColorScheme={loaderData.defaultColorScheme} />
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 cab4412e61..aeeb1daa2d 100644
--- a/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx
+++ b/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx
@@ -104,6 +104,7 @@ import {
 	ReviewItemDisplay,
 } from "~/components/common";
 import {
+	BaseEntityDisplay,
 	MediaScrollArea,
 	PartialMetadataDisplay,
 	ToggleMediaMonitorMenuItem,
@@ -274,6 +275,7 @@ export default function Page() {
 
 	const inProgress = loaderData.userMetadataDetails.inProgress;
 	const nextEntry = loaderData.userMetadataDetails.nextEntry;
+	const firstGroupAssociated = loaderData.metadataDetails.group.at(0);
 
 	const onSubmitProgressUpdate = (e: React.FormEvent<HTMLFormElement>) => {
 		submit(e);
@@ -332,23 +334,22 @@ export default function Page() {
 				<MediaDetailsLayout
 					images={loaderData.metadataDetails.assets.images}
 					externalLink={{
-						source: loaderData.metadataDetails.source,
 						lot: loaderData.metadataDetails.lot,
+						source: loaderData.metadataDetails.source,
 						href: loaderData.metadataDetails.sourceUrl,
 					}}
 				>
 					<Box>
 						{userPreferences.featuresEnabled.media.groups &&
-						loaderData.metadataDetails.group ? (
+						firstGroupAssociated ? (
 							<Link
+								style={{ color: "unset" }}
 								to={$path("/media/groups/item/:id", {
-									id: loaderData.metadataDetails.group.id,
+									id: firstGroupAssociated.id,
 								})}
-								style={{ color: "unset" }}
 							>
 								<Text c="dimmed" fs="italic">
-									{loaderData.metadataDetails.group.name} #
-									{loaderData.metadataDetails.group.part}
+									{firstGroupAssociated.name} #{firstGroupAssociated.part}
 								</Text>
 							</Link>
 						) : null}
@@ -675,29 +676,13 @@ export default function Page() {
 													>
 														<Flex gap="md">
 															{c.items.map((creator) => (
-																<Box key={`${creator.id}-${creator.name}`}>
-																	{creator.id ? (
-																		<Anchor
-																			component={Link}
-																			data-creator-id={creator.id}
-																			to={$path("/media/people/item/:id", {
-																				id: creator.id,
-																			})}
-																		>
-																			<MetadataCreator
-																				name={creator.name}
-																				image={creator.image}
-																				character={creator.character}
-																			/>
-																		</Anchor>
-																	) : (
-																		<MetadataCreator
-																			name={creator.name}
-																			image={creator.image}
-																			character={creator.character}
-																		/>
-																	)}
-																</Box>
+																<MetadataCreator
+																	id={creator.id}
+																	name={creator.name}
+																	image={creator.image}
+																	character={creator.character}
+																	key={`${creator.id}-${creator.name}`}
+																/>
 															))}
 														</Flex>
 													</ScrollArea>
@@ -1287,28 +1272,18 @@ const DisplayShowSeasonEpisodes = (props: {
 
 const MetadataCreator = (props: {
 	name: string;
+	id?: string | null;
 	image?: string | null;
 	character?: string | null;
-}) => {
-	return (
-		<>
-			<Avatar
-				imageProps={{ loading: "lazy" }}
-				src={props.image}
-				h={100}
-				w={85}
-				radius="sm"
-				mx="auto"
-				alt={`${props.name} profile picture`}
-				styles={{ image: { objectPosition: "top" } }}
-			/>
-			<Text size="xs" c="dimmed" ta="center" lineClamp={3} mt={4}>
-				{props.name}
-				{props.character ? ` as ${props.character}` : null}
-			</Text>
-		</>
-	);
-};
+}) => (
+	<BaseEntityDisplay
+		image={props.image || undefined}
+		title={`${props.name} ${props.character ? `as ${props.character}` : ""}`}
+		link={
+			props.id ? $path("/media/people/item/:id", { id: props.id }) : undefined
+		}
+	/>
+);
 
 type History =
 	UserMetadataDetailsQuery["userMetadataDetails"]["history"][number];
diff --git a/apps/frontend/app/routes/_dashboard.media.people.item.$id._index.tsx b/apps/frontend/app/routes/_dashboard.media.people.item.$id._index.tsx
index 76e4df0f30..13790ad384 100644
--- a/apps/frontend/app/routes/_dashboard.media.people.item.$id._index.tsx
+++ b/apps/frontend/app/routes/_dashboard.media.people.item.$id._index.tsx
@@ -23,9 +23,12 @@ import { sum } from "@ryot/ts-utils";
 import {
 	IconDeviceTv,
 	IconInfoCircle,
+	IconLibrary,
 	IconMessageCircle2,
 	IconUser,
 } from "@tabler/icons-react";
+import { useQuery } from "@tanstack/react-query";
+import { $path } from "remix-routes";
 import { useLocalStorage } from "usehooks-ts";
 import { z } from "zod";
 import { zx } from "zodix";
@@ -35,10 +38,12 @@ import {
 	ReviewItemDisplay,
 } from "~/components/common";
 import {
+	BaseEntityDisplay,
 	MediaScrollArea,
 	PartialMetadataDisplay,
 	ToggleMediaMonitorMenuItem,
 } from "~/components/media";
+import { getMetadataGroupDetailsQuery } from "~/lib/generals";
 import { useUserPreferences } from "~/lib/hooks";
 import { useAddEntityToCollection, useReviewEntity } from "~/lib/state/media";
 import { serverGqlService } from "~/lib/utilities.server";
@@ -74,13 +79,23 @@ export default function Page() {
 	const userPreferences = useUserPreferences();
 	const [_r, setEntityToReview] = useReviewEntity();
 	const [_a, setAddEntityToCollectionData] = useAddEntityToCollection();
-	const [roleFilter, setRoleFilter] = useLocalStorage(
+	const [mediaRoleFilter, setMediaRoleFilter] = useLocalStorage(
 		"MediaTabRoleFilter",
-		loaderData.personDetails.contents.map((c) => c.name).at(0) || null,
+		loaderData.personDetails.associatedMetadata.map((c) => c.name).at(0) ||
+			null,
+	);
+	const [groupRoleFilter, setGroupRoleFilter] = useLocalStorage(
+		"MediaTabRoleFilter",
+		loaderData.personDetails.associatedMetadataGroups
+			.map((c) => c.name)
+			.at(0) || null,
 	);
 
 	const totalMetadata = sum(
-		loaderData.personDetails.contents.map((c) => c.count),
+		loaderData.personDetails.associatedMetadata.map((c) => c.count),
+	);
+	const totalMetadataGroups = sum(
+		loaderData.personDetails.associatedMetadataGroups.map((c) => c.count),
 	);
 
 	return (
@@ -97,7 +112,8 @@ export default function Page() {
 				</Title>
 				<Text c="dimmed" fz={{ base: "sm", lg: "md" }}>
 					{[
-						`${totalMetadata} media items`,
+						totalMetadata ? `${totalMetadata} media items` : null,
+						totalMetadataGroups ? `${totalMetadataGroups} groups` : null,
 						loaderData.personDetails.details.birthDate &&
 							`Birth: ${loaderData.personDetails.details.birthDate}`,
 						loaderData.personDetails.details.deathDate &&
@@ -149,6 +165,11 @@ export default function Page() {
 								Media
 							</Tabs.Tab>
 						) : null}
+						{totalMetadataGroups > 0 ? (
+							<Tabs.Tab value="groups" leftSection={<IconLibrary size={16} />}>
+								Groups
+							</Tabs.Tab>
+						) : null}
 						{loaderData.personDetails.details.description ? (
 							<Tabs.Tab
 								value="overview"
@@ -169,37 +190,75 @@ export default function Page() {
 							Actions
 						</Tabs.Tab>
 					</Tabs.List>
-					<Tabs.Panel value="media">
-						<MediaScrollArea>
-							<Stack gap="xl">
-								<Group justify="center">
-									<Text size="sm" c="dimmed">
-										Role:
-									</Text>
-									<Select
-										size="xs"
-										value={roleFilter}
-										onChange={(value) => setRoleFilter(value)}
-										data={loaderData.personDetails.contents.map((c) => ({
-											value: c.name,
-											label: `${c.name} (${c.count})`,
-										}))}
-									/>
-								</Group>
-								<SimpleGrid cols={{ base: 3, md: 4, lg: 5 }}>
-									{loaderData.personDetails.contents
-										.find((c) => c.name === roleFilter)
-										?.items.map((item) => (
-											<MetadataDisplay
-												key={item.metadataId}
-												character={item.character}
-												metadataId={item.metadataId}
-											/>
-										))}
-								</SimpleGrid>
-							</Stack>
-						</MediaScrollArea>
-					</Tabs.Panel>
+					{totalMetadata > 0 ? (
+						<Tabs.Panel value="media">
+							<MediaScrollArea>
+								<Stack gap="xl">
+									<Group justify="center">
+										<Text size="sm" c="dimmed">
+											Role:
+										</Text>
+										<Select
+											size="xs"
+											value={mediaRoleFilter}
+											onChange={(value) => setMediaRoleFilter(value)}
+											data={loaderData.personDetails.associatedMetadata.map(
+												(c) => ({
+													value: c.name,
+													label: `${c.name} (${c.count})`,
+												}),
+											)}
+										/>
+									</Group>
+									<SimpleGrid cols={{ base: 3, md: 4, lg: 5 }}>
+										{loaderData.personDetails.associatedMetadata
+											.find((c) => c.name === mediaRoleFilter)
+											?.items.map((item) => (
+												<MetadataDisplay
+													key={item.entityId}
+													character={item.character}
+													metadataId={item.entityId}
+												/>
+											))}
+									</SimpleGrid>
+								</Stack>
+							</MediaScrollArea>
+						</Tabs.Panel>
+					) : null}
+					{totalMetadataGroups > 0 ? (
+						<Tabs.Panel value="groups">
+							<MediaScrollArea>
+								<Stack gap="xl">
+									<Group justify="center">
+										<Text size="sm" c="dimmed">
+											Role:
+										</Text>
+										<Select
+											size="xs"
+											value={groupRoleFilter}
+											onChange={(value) => setGroupRoleFilter(value)}
+											data={loaderData.personDetails.associatedMetadataGroups.map(
+												(c) => ({
+													value: c.name,
+													label: `${c.name} (${c.count})`,
+												}),
+											)}
+										/>
+									</Group>
+									<SimpleGrid cols={{ base: 3, md: 4, lg: 5 }}>
+										{loaderData.personDetails.associatedMetadataGroups
+											.find((c) => c.name === groupRoleFilter)
+											?.items.map((item) => (
+												<MetadataGroupDisplay
+													key={item.entityId}
+													metadataGroupId={item.entityId}
+												/>
+											))}
+									</SimpleGrid>
+								</Stack>
+							</MediaScrollArea>
+						</Tabs.Panel>
+					) : null}
 					{loaderData.personDetails.details.description ? (
 						<Tabs.Panel value="overview">
 							<MediaScrollArea>
@@ -298,3 +357,19 @@ const MetadataDisplay = (props: {
 		/>
 	);
 };
+
+const MetadataGroupDisplay = (props: {
+	metadataGroupId: string;
+}) => {
+	const { data: metadataGroupDetails } = useQuery(
+		getMetadataGroupDetailsQuery(props.metadataGroupId),
+	);
+
+	return (
+		<BaseEntityDisplay
+			title={metadataGroupDetails?.details.title}
+			image={metadataGroupDetails?.details.displayImages[0]}
+			link={$path("/media/groups/item/:id", { id: props.metadataGroupId })}
+		/>
+	);
+};
diff --git a/apps/frontend/app/routes/_dashboard.settings.preferences.tsx b/apps/frontend/app/routes/_dashboard.settings.preferences.tsx
index 7751988158..bd99cdbaef 100644
--- a/apps/frontend/app/routes/_dashboard.settings.preferences.tsx
+++ b/apps/frontend/app/routes/_dashboard.settings.preferences.tsx
@@ -451,9 +451,13 @@ export default function Page() {
 													"A new public review is posted for media/people you monitor",
 											)
 											.with(
-												MediaStateChanged.PersonMediaAssociated,
+												MediaStateChanged.PersonMetadataAssociated,
 												() => "New media is associated with a person",
 											)
+											.with(
+												MediaStateChanged.PersonMetadataGroupAssociated,
+												() => "New media group is associated with a person",
+											)
 											.exhaustive()}
 									/>
 								))}
diff --git a/apps/frontend/public/icons/maskable_icon_x180.png b/apps/frontend/public/icons/maskable_icon_x180.png
new file mode 100644
index 0000000000..9ff1d603bf
Binary files /dev/null and b/apps/frontend/public/icons/maskable_icon_x180.png differ
diff --git a/apps/frontend/public/manifest.json b/apps/frontend/public/manifest.json
index 238c3882de..99adca7f86 100644
--- a/apps/frontend/public/manifest.json
+++ b/apps/frontend/public/manifest.json
@@ -38,6 +38,12 @@
 			"src": "icons/maskable_icon_x128.png",
 			"type": "image/png"
 		},
+		{
+			"purpose": "maskable",
+			"sizes": "180x180",
+			"src": "icons/maskable_icon_x180.png",
+			"type": "image/png"
+		},
 		{
 			"purpose": "maskable",
 			"sizes": "192x192",
diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs
index bc7435521e..a7c1c24a5d 100644
--- a/crates/migrations/src/lib.rs
+++ b/crates/migrations/src/lib.rs
@@ -57,6 +57,7 @@ mod m20241215_changes_for_issue_1131;
 mod m20241220_changes_for_issue_1141;
 mod m20241220_changes_for_issue_49;
 mod m20241224_changes_for_issue_1139;
+mod m20241227_changes_for_issue_1157;
 
 pub use m20230410_create_metadata::Metadata as AliasedMetadata;
 pub use m20230413_create_person::Person as AliasedPerson;
@@ -82,9 +83,9 @@ impl MigratorTrait for Migrator {
             Box::new(m20230409_create_extensions::Migration),
             Box::new(m20230417_create_user::Migration),
             Box::new(m20230410_create_metadata::Migration),
+            Box::new(m20230501_create_metadata_group::Migration),
             Box::new(m20230413_create_person::Migration),
             Box::new(m20230419_create_seen::Migration),
-            Box::new(m20230501_create_metadata_group::Migration),
             Box::new(m20230502_create_genre::Migration),
             Box::new(m20230504_create_collection::Migration),
             Box::new(m20230822_create_exercise::Migration),
@@ -135,6 +136,7 @@ impl MigratorTrait for Migrator {
             Box::new(m20241220_changes_for_issue_49::Migration),
             Box::new(m20241220_changes_for_issue_1141::Migration),
             Box::new(m20241224_changes_for_issue_1139::Migration),
+            Box::new(m20241227_changes_for_issue_1157::Migration),
         ]
     }
 }
diff --git a/crates/migrations/src/m20230413_create_person.rs b/crates/migrations/src/m20230413_create_person.rs
index f1d5adf3f2..61be1b428c 100644
--- a/crates/migrations/src/m20230413_create_person.rs
+++ b/crates/migrations/src/m20230413_create_person.rs
@@ -1,6 +1,6 @@
 use sea_orm_migration::prelude::*;
 
-use super::m20230410_create_metadata::Metadata;
+use super::{m20230410_create_metadata::Metadata, m20230501_create_metadata_group::MetadataGroup};
 
 #[derive(DeriveMigrationName)]
 pub struct Migration;
@@ -33,13 +33,81 @@ pub enum Person {
 
 #[derive(Iden)]
 pub enum MetadataToPerson {
-    Table,
-    MetadataId,
-    PersonId,
     Role,
+    Table,
     // The order in which the person will be displayed
     Index,
+    PersonId,
     Character,
+    MetadataId,
+}
+
+#[derive(Iden)]
+pub enum MetadataGroupToPerson {
+    Role,
+    Table,
+    Index,
+    PersonId,
+    MetadataGroupId,
+}
+
+// FIXME: Remove this function and move it to the main function.
+pub async fn create_metadata_group_to_person_table(
+    manager: &SchemaManager<'_>,
+) -> Result<(), DbErr> {
+    manager
+        .create_table(
+            Table::create()
+                .table(MetadataGroupToPerson::Table)
+                .col(
+                    ColumnDef::new(MetadataGroupToPerson::Role)
+                        .text()
+                        .not_null(),
+                )
+                .col(
+                    ColumnDef::new(MetadataGroupToPerson::PersonId)
+                        .text()
+                        .not_null(),
+                )
+                .col(
+                    ColumnDef::new(MetadataGroupToPerson::MetadataGroupId)
+                        .text()
+                        .not_null(),
+                )
+                .col(ColumnDef::new(MetadataGroupToPerson::Index).integer())
+                .primary_key(
+                    Index::create()
+                        .name("metadata_group_to_person-primary-key")
+                        .col(MetadataGroupToPerson::MetadataGroupId)
+                        .col(MetadataGroupToPerson::PersonId)
+                        .col(MetadataGroupToPerson::Role),
+                )
+                .foreign_key(
+                    ForeignKey::create()
+                        .name("fk-metadata_group-item_person-person_id")
+                        .from(
+                            MetadataGroupToPerson::Table,
+                            MetadataGroupToPerson::PersonId,
+                        )
+                        .to(Person::Table, Person::Id)
+                        .on_delete(ForeignKeyAction::Cascade)
+                        .on_update(ForeignKeyAction::Cascade),
+                )
+                .foreign_key(
+                    ForeignKey::create()
+                        .name("fk-metadata_group-item_person-metadata_group_id")
+                        .from(
+                            MetadataGroupToPerson::Table,
+                            MetadataGroupToPerson::MetadataGroupId,
+                        )
+                        .to(MetadataGroup::Table, MetadataGroup::Id)
+                        .on_delete(ForeignKeyAction::Cascade)
+                        .on_update(ForeignKeyAction::Cascade),
+                )
+                .to_owned(),
+        )
+        .await?;
+    Ok(())
 }
 
 #[async_trait::async_trait]
@@ -131,6 +199,7 @@ impl MigrationTrait for Migration {
                     .to_owned(),
             )
             .await?;
+        create_metadata_group_to_person_table(manager).await?;
         Ok(())
     }
 
diff --git a/crates/migrations/src/m20230501_create_metadata_group.rs b/crates/migrations/src/m20230501_create_metadata_group.rs
index c03fce9df9..6983f2de40 100644
--- a/crates/migrations/src/m20230501_create_metadata_group.rs
+++ b/crates/migrations/src/m20230501_create_metadata_group.rs
@@ -1,3 +1,4 @@
+// FIXME: Rename this to m20230411_create_metadata_group in the next major release
 use sea_orm_migration::prelude::*;
 
 #[derive(DeriveMigrationName)]
@@ -32,7 +33,12 @@ impl MigrationTrait for Migration {
                             .primary_key(),
                     )
                     .col(ColumnDef::new(MetadataGroup::Identifier).text().not_null())
-                    .col(ColumnDef::new(MetadataGroup::Parts).integer().not_null())
+                    .col(
+                        ColumnDef::new(MetadataGroup::Parts)
+                            .integer()
+                            .default(0)
+                            .not_null(),
+                    )
                     .col(ColumnDef::new(MetadataGroup::Title).text().not_null())
                     .col(ColumnDef::new(MetadataGroup::Description).text())
                     .col(ColumnDef::new(MetadataGroup::Lot).text().not_null())
diff --git a/crates/migrations/src/m20241227_changes_for_issue_1157.rs b/crates/migrations/src/m20241227_changes_for_issue_1157.rs
new file mode 100644
index 0000000000..6ade759355
--- /dev/null
+++ b/crates/migrations/src/m20241227_changes_for_issue_1157.rs
@@ -0,0 +1,56 @@
+use sea_orm_migration::prelude::*;
+
+use crate::m20230413_create_person::create_metadata_group_to_person_table;
+
+#[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_table("metadata_group_to_person").await? {
+            create_metadata_group_to_person_table(manager).await?;
+        }
+        db.execute_unprepared(
+            r#"
+UPDATE
+  "user"
+SET
+  preferences = JSONB_SET(
+    preferences,
+    '{notifications,to_send}',
+    (
+      (preferences -> 'notifications' -> 'to_send')
+        || '"PersonMetadataAssociated"' || '"PersonMetadataGroupAssociated"'
+    ) - 'PersonMediaAssociated'
+  )
+WHERE
+  preferences -> 'notifications' -> 'to_send' ? 'PersonMediaAssociated'
+            "#,
+        )
+        .await?;
+        db.execute_unprepared(
+            r#"
+UPDATE
+  "person"
+SET
+  "state_changes" = JSONB_SET(
+    JSONB_SET("state_changes", '{metadata_associated}', "state_changes"->'media_associated') - 'media_associated',
+    '{metadata_groups_associated}',
+    '[]'
+  )
+WHERE
+  "state_changes"->'media_associated' is not null;
+"#,
+        )
+        .await?;
+        db.execute_unprepared(r#"ALTER TABLE "metadata_group" ALTER COLUMN "parts" SET DEFAULT 0"#)
+            .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 0dcecbca86..fc64edf1b8 100644
--- a/crates/models/common/src/lib.rs
+++ b/crates/models/common/src/lib.rs
@@ -180,12 +180,13 @@ pub struct ChangeCollectionToEntityInput {
 pub enum MediaStateChanged {
     ReviewPosted,
     MetadataPublished,
-    PersonMediaAssociated,
     MetadataStatusChanged,
     MetadataEpisodeReleased,
+    PersonMetadataAssociated,
     MetadataReleaseDateChanged,
     MetadataEpisodeNameChanged,
     MetadataEpisodeImagesChanged,
+    PersonMetadataGroupAssociated,
     MetadataNumberOfSeasonsChanged,
     MetadataChaptersOrEpisodesChanged,
 }
diff --git a/crates/models/database/src/lib.rs b/crates/models/database/src/lib.rs
index badc0fb457..53f8dd05f3 100644
--- a/crates/models/database/src/lib.rs
+++ b/crates/models/database/src/lib.rs
@@ -15,6 +15,7 @@ pub mod import_report;
 pub mod integration;
 pub mod metadata;
 pub mod metadata_group;
+pub mod metadata_group_to_person;
 pub mod metadata_to_genre;
 pub mod metadata_to_metadata;
 pub mod metadata_to_metadata_group;
diff --git a/crates/models/database/src/metadata_group.rs b/crates/models/database/src/metadata_group.rs
index 66f3b60bf2..b1458d8190 100644
--- a/crates/models/database/src/metadata_group.rs
+++ b/crates/models/database/src/metadata_group.rs
@@ -13,7 +13,10 @@ use serde::{Deserialize, Serialize};
 #[sea_orm(table_name = "metadata_group")]
 #[graphql(name = "MetadataGroup")]
 #[boilermates("MetadataGroupWithoutId")]
-#[boilermates(attr_for("MetadataGroupWithoutId", "#[derive(Clone, Default, Debug)]"))]
+#[boilermates(attr_for(
+    "MetadataGroupWithoutId",
+    "#[derive(Clone, Default, Debug, Serialize, Deserialize, Hash)]"
+))]
 pub struct Model {
     #[boilermates(not_in("MetadataGroupWithoutId"))]
     #[sea_orm(primary_key, auto_increment = false)]
@@ -38,6 +41,8 @@ pub struct Model {
 pub enum Relation {
     #[sea_orm(has_many = "super::collection_to_entity::Entity")]
     CollectionToEntity,
+    #[sea_orm(has_many = "super::metadata_group_to_person::Entity")]
+    MetadataGroupToPerson,
     #[sea_orm(has_many = "super::metadata_to_metadata_group::Entity")]
     MetadataToMetadataGroup,
     #[sea_orm(has_many = "super::review::Entity")]
@@ -52,6 +57,12 @@ impl Related<super::collection_to_entity::Entity> for Entity {
     }
 }
 
+impl Related<super::metadata_group_to_person::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::MetadataGroupToPerson.def()
+    }
+}
+
 impl Related<super::metadata_to_metadata_group::Entity> for Entity {
     fn to() -> RelationDef {
         Relation::MetadataToMetadataGroup.def()
diff --git a/crates/models/database/src/metadata_group_to_person.rs b/crates/models/database/src/metadata_group_to_person.rs
new file mode 100644
index 0000000000..5b122573b3
--- /dev/null
+++ b/crates/models/database/src/metadata_group_to_person.rs
@@ -0,0 +1,49 @@
+//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
+
+use sea_orm::entity::prelude::*;
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[sea_orm(table_name = "metadata_group_to_person")]
+pub struct Model {
+    pub index: i32,
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub role: String,
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub person_id: String,
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub metadata_group_id: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::metadata_group::Entity",
+        from = "Column::MetadataGroupId",
+        to = "super::metadata_group::Column::Id",
+        on_update = "Cascade",
+        on_delete = "Cascade"
+    )]
+    MetadataGroup,
+    #[sea_orm(
+        belongs_to = "super::person::Entity",
+        from = "Column::PersonId",
+        to = "super::person::Column::Id",
+        on_update = "Cascade",
+        on_delete = "Cascade"
+    )]
+    Person,
+}
+
+impl Related<super::metadata_group::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::MetadataGroup.def()
+    }
+}
+
+impl Related<super::person::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Person.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
diff --git a/crates/models/database/src/person.rs b/crates/models/database/src/person.rs
index c453208295..4aa017b4a7 100644
--- a/crates/models/database/src/person.rs
+++ b/crates/models/database/src/person.rs
@@ -44,6 +44,8 @@ pub struct Model {
 pub enum Relation {
     #[sea_orm(has_many = "super::collection_to_entity::Entity")]
     CollectionToEntity,
+    #[sea_orm(has_many = "super::metadata_group_to_person::Entity")]
+    MetadataGroupToPerson,
     #[sea_orm(has_many = "super::metadata_to_person::Entity")]
     MetadataToPerson,
     #[sea_orm(has_many = "super::review::Entity")]
@@ -58,6 +60,12 @@ impl Related<super::collection_to_entity::Entity> for Entity {
     }
 }
 
+impl Related<super::metadata_group_to_person::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::MetadataGroupToPerson.def()
+    }
+}
+
 impl Related<super::metadata_to_person::Entity> for Entity {
     fn to() -> RelationDef {
         Relation::MetadataToPerson.def()
diff --git a/crates/models/database/src/prelude.rs b/crates/models/database/src/prelude.rs
index 78836630f1..dad805a837 100644
--- a/crates/models/database/src/prelude.rs
+++ b/crates/models/database/src/prelude.rs
@@ -12,6 +12,7 @@ pub use super::import_report::Entity as ImportReport;
 pub use super::integration::Entity as Integration;
 pub use super::metadata::Entity as Metadata;
 pub use super::metadata_group::Entity as MetadataGroup;
+pub use super::metadata_group_to_person::Entity as MetadataGroupToPerson;
 pub use super::metadata_to_genre::Entity as MetadataToGenre;
 pub use super::metadata_to_metadata::Entity as MetadataToMetadata;
 pub use super::metadata_to_metadata_group::Entity as MetadataToMetadataGroup;
diff --git a/crates/models/dependent/src/lib.rs b/crates/models/dependent/src/lib.rs
index f358e8d48a..244bb91cfb 100644
--- a/crates/models/dependent/src/lib.rs
+++ b/crates/models/dependent/src/lib.rs
@@ -4,12 +4,13 @@ use async_graphql::{InputObject, OutputType, SimpleObject, Union};
 use chrono::NaiveDate;
 use common_models::{
     ApplicationDateRange, BackendError, DailyUserActivitiesResponseGroupedBy,
-    DailyUserActivityHourRecord, SearchDetails,
+    DailyUserActivityHourRecord, PersonSourceSpecifics, SearchDetails,
 };
 use config::FrontendConfig;
 use database_models::{
-    collection, exercise, metadata, metadata_group, person, seen, user, user_measurement,
-    user_to_entity, workout, workout_template,
+    collection, exercise, metadata,
+    metadata_group::{self, MetadataGroupWithoutId},
+    person, seen, user, user_measurement, user_to_entity, workout, workout_template,
 };
 use enum_models::{
     ExerciseEquipment, ExerciseForce, ExerciseLevel, ExerciseLot, ExerciseMechanic, ExerciseMuscle,
@@ -21,9 +22,9 @@ use media_models::{
     CollectionItem, CreateOrUpdateCollectionInput, EntityWithLot, GenreListItem,
     GraphqlMediaAssets, ImportOrExportExerciseItem, ImportOrExportMetadataGroupItem,
     ImportOrExportMetadataItem, ImportOrExportPersonItem, MetadataCreatorGroupedByRole,
-    MetadataGroupSearchItem, MetadataSearchItem, PeopleSearchItem, PersonDetailsGroupedByRole,
-    ReviewItem, UserDetailsError, UserMediaNextEntry, UserMetadataDetailsEpisodeProgress,
-    UserMetadataDetailsShowSeasonProgress,
+    MetadataGroupSearchItem, MetadataSearchItem, PartialMetadataWithoutId, PeopleSearchItem,
+    PersonDetailsGroupedByRole, ReviewItem, UserDetailsError, UserMediaNextEntry,
+    UserMetadataDetailsEpisodeProgress, UserMetadataDetailsShowSeasonProgress,
 };
 use rust_decimal::Decimal;
 use schematic::Schematic;
@@ -108,6 +109,37 @@ pub struct UserExerciseDetails {
     pub history: Option<Vec<UserToExerciseHistoryExtraInformation>>,
 }
 
+#[derive(Debug, Default, Serialize, Deserialize, Clone, Hash)]
+pub struct MetadataPersonRelated {
+    pub role: String,
+    pub character: Option<String>,
+    pub metadata: PartialMetadataWithoutId,
+}
+
+#[derive(Debug, Default, Serialize, Deserialize, Clone, Hash)]
+pub struct MetadataGroupPersonRelated {
+    pub role: String,
+    pub metadata_group: MetadataGroupWithoutId,
+}
+
+#[derive(Debug, Default, Serialize, Deserialize, Clone, Hash)]
+pub struct PersonDetails {
+    pub name: String,
+    pub identifier: String,
+    pub source: MediaSource,
+    pub place: Option<String>,
+    pub gender: Option<String>,
+    pub website: Option<String>,
+    pub source_url: Option<String>,
+    pub description: Option<String>,
+    pub images: Option<Vec<String>>,
+    pub death_date: Option<NaiveDate>,
+    pub birth_date: Option<NaiveDate>,
+    pub related_metadata: Vec<MetadataPersonRelated>,
+    pub source_specifics: Option<PersonSourceSpecifics>,
+    pub related_metadata_groups: Vec<MetadataGroupPersonRelated>,
+}
+
 #[derive(Clone, Debug, Deserialize, Serialize, InputObject)]
 pub struct UpdateCustomExerciseInput {
     #[graphql(flatten)]
@@ -130,9 +162,10 @@ pub struct CollectionContents {
 }
 
 #[derive(Debug, Serialize, Deserialize, SimpleObject, Clone)]
-pub struct PersonDetails {
+pub struct GraphqlPersonDetails {
     pub details: person::Model,
-    pub contents: Vec<PersonDetailsGroupedByRole>,
+    pub associated_metadata: Vec<PersonDetailsGroupedByRole>,
+    pub associated_metadata_groups: Vec<PersonDetailsGroupedByRole>,
 }
 
 #[derive(Debug, Serialize, Deserialize, SimpleObject, Clone)]
diff --git a/crates/models/media/src/lib.rs b/crates/models/media/src/lib.rs
index 9d42f1e642..de51b01a9d 100644
--- a/crates/models/media/src/lib.rs
+++ b/crates/models/media/src/lib.rs
@@ -408,30 +408,6 @@ pub struct PartialMetadataPerson {
     pub source_specifics: Option<PersonSourceSpecifics>,
 }
 
-#[derive(Debug, Default, Serialize, Deserialize, Clone, Hash)]
-pub struct MetadataPersonRelated {
-    pub role: String,
-    pub character: Option<String>,
-    pub metadata: PartialMetadataWithoutId,
-}
-
-#[derive(Debug, Default, Serialize, Deserialize, Clone, Hash)]
-pub struct MetadataPerson {
-    pub name: String,
-    pub identifier: String,
-    pub source: MediaSource,
-    pub gender: Option<String>,
-    pub place: Option<String>,
-    pub website: Option<String>,
-    pub source_url: Option<String>,
-    pub description: Option<String>,
-    pub images: Option<Vec<String>>,
-    pub death_date: Option<NaiveDate>,
-    pub birth_date: Option<NaiveDate>,
-    pub related: Vec<MetadataPersonRelated>,
-    pub source_specifics: Option<PersonSourceSpecifics>,
-}
-
 #[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, SimpleObject, Hash)]
 pub struct MetadataImageForMediaDetails {
     pub image: String,
@@ -455,38 +431,38 @@ pub struct MetadataExternalIdentifiers {
 
 #[derive(Debug, Serialize, Deserialize, Clone, Default)]
 pub struct MetadataDetails {
-    pub identifier: String,
-    pub is_nsfw: Option<bool>,
+    pub lot: MediaLot,
     pub title: String,
+    pub identifier: String,
+    pub genres: Vec<String>,
     pub source: MediaSource,
+    pub is_nsfw: Option<bool>,
+    pub publish_year: Option<i32>,
+    pub videos: Vec<MetadataVideo>,
     pub source_url: Option<String>,
     pub description: Option<String>,
+    pub groups: Vec<CommitMediaInput>,
+    pub publish_date: Option<NaiveDate>,
+    pub provider_rating: Option<Decimal>,
     pub original_language: Option<String>,
-    pub lot: MediaLot,
     pub production_status: Option<String>,
     pub creators: Vec<MetadataFreeCreator>,
     pub people: Vec<PartialMetadataPerson>,
-    pub genres: Vec<String>,
-    pub url_images: Vec<MetadataImageForMediaDetails>,
-    pub s3_images: Vec<MetadataImageForMediaDetails>,
-    pub videos: Vec<MetadataVideo>,
-    pub publish_year: Option<i32>,
-    pub publish_date: Option<NaiveDate>,
-    pub suggestions: Vec<PartialMetadataWithoutId>,
-    pub groups: Vec<CommitMediaInput>,
-    pub provider_rating: Option<Decimal>,
     pub watch_providers: Vec<WatchProvider>,
-    pub audio_book_specifics: Option<AudioBookSpecifics>,
+    pub show_specifics: Option<ShowSpecifics>,
     pub book_specifics: Option<BookSpecifics>,
-    pub external_identifiers: Option<MetadataExternalIdentifiers>,
     pub movie_specifics: Option<MovieSpecifics>,
-    pub podcast_specifics: Option<PodcastSpecifics>,
-    pub show_specifics: Option<ShowSpecifics>,
-    pub video_game_specifics: Option<VideoGameSpecifics>,
-    pub visual_novel_specifics: Option<VisualNovelSpecifics>,
     pub anime_specifics: Option<AnimeSpecifics>,
     pub manga_specifics: Option<MangaSpecifics>,
     pub music_specifics: Option<MusicSpecifics>,
+    pub suggestions: Vec<PartialMetadataWithoutId>,
+    pub podcast_specifics: Option<PodcastSpecifics>,
+    pub s3_images: Vec<MetadataImageForMediaDetails>,
+    pub url_images: Vec<MetadataImageForMediaDetails>,
+    pub audio_book_specifics: Option<AudioBookSpecifics>,
+    pub video_game_specifics: Option<VideoGameSpecifics>,
+    pub visual_novel_specifics: Option<VisualNovelSpecifics>,
+    pub external_identifiers: Option<MetadataExternalIdentifiers>,
 }
 
 /// A specific instance when an entity was seen.
@@ -858,7 +834,8 @@ pub struct MediaAssociatedPersonStateChanges {
 
 #[derive(Debug, Serialize, Deserialize, Clone, FromJsonQueryResult, Eq, PartialEq, Default)]
 pub struct PersonStateChanges {
-    pub media_associated: HashSet<MediaAssociatedPersonStateChanges>,
+    pub metadata_associated: HashSet<MediaAssociatedPersonStateChanges>,
+    pub metadata_groups_associated: HashSet<MediaAssociatedPersonStateChanges>,
 }
 
 #[skip_serializing_none]
@@ -1206,9 +1183,9 @@ pub struct MetadataCreatorGroupedByRole {
     pub items: Vec<MetadataCreator>,
 }
 
-#[derive(Debug, Serialize, Deserialize, SimpleObject, Clone)]
+#[derive(Debug, Serialize, Default, Deserialize, SimpleObject, Clone)]
 pub struct PersonDetailsItemWithCharacter {
-    pub metadata_id: String,
+    pub entity_id: String,
     pub character: Option<String>,
 }
 
@@ -1257,11 +1234,11 @@ pub struct GraphqlMetadataDetails {
     pub assets: GraphqlMediaAssets,
     pub description: Option<String>,
     pub publish_date: Option<NaiveDate>,
+    pub group: Vec<GraphqlMetadataGroup>,
     pub provider_rating: Option<Decimal>,
     pub original_language: Option<String>,
     pub production_status: Option<String>,
     pub created_by_user_id: Option<String>,
-    pub group: Option<GraphqlMetadataGroup>,
     pub watch_providers: Vec<WatchProvider>,
     pub show_specifics: Option<ShowSpecifics>,
     pub book_specifics: Option<BookSpecifics>,
diff --git a/crates/providers/src/anilist/mod.rs b/crates/providers/src/anilist/mod.rs
index 9b1177e7de..8c11d6a06c 100644
--- a/crates/providers/src/anilist/mod.rs
+++ b/crates/providers/src/anilist/mod.rs
@@ -5,15 +5,16 @@ use chrono::NaiveDate;
 use common_models::{PersonSourceSpecifics, SearchDetails, StoredUrl};
 use common_utils::PAGE_SIZE;
 use config::AnilistPreferredLanguage;
-use dependent_models::{PeopleSearchResponse, SearchResults};
+use dependent_models::{
+    PersonDetails, MetadataPersonRelated, PeopleSearchResponse, SearchResults,
+};
 use enum_models::{MediaLot, MediaSource};
 use graphql_client::{GraphQLQuery, Response};
 use itertools::Itertools;
 use media_models::{
     AnimeAiringScheduleSpecifics, AnimeSpecifics, MangaSpecifics, MetadataDetails,
-    MetadataImageForMediaDetails, MetadataPerson, MetadataPersonRelated, MetadataSearchItem,
-    MetadataVideo, MetadataVideoSource, PartialMetadataPerson, PartialMetadataWithoutId,
-    PeopleSearchItem,
+    MetadataImageForMediaDetails, MetadataSearchItem, MetadataVideo, MetadataVideoSource,
+    PartialMetadataPerson, PartialMetadataWithoutId, PeopleSearchItem,
 };
 use reqwest::Client;
 use rust_decimal::Decimal;
@@ -233,7 +234,7 @@ impl MediaProvider for NonMediaAnilistService {
         &self,
         identity: &str,
         source_specifics: &Option<PersonSourceSpecifics>,
-    ) -> Result<MetadataPerson> {
+    ) -> Result<PersonDetails> {
         let is_studio = matches!(
             source_specifics,
             Some(PersonSourceSpecifics {
@@ -261,7 +262,7 @@ impl MediaProvider for NonMediaAnilistService {
                 .unwrap()
                 .studio
                 .unwrap();
-            let related = details
+            let related_metadata = details
                 .media
                 .unwrap()
                 .edges
@@ -287,8 +288,8 @@ impl MediaProvider for NonMediaAnilistService {
                     }
                 })
                 .collect();
-            MetadataPerson {
-                related,
+            PersonDetails {
+                related_metadata,
                 name: details.name,
                 website: details.site_url,
                 source: MediaSource::Anilist,
@@ -339,7 +340,7 @@ impl MediaProvider for NonMediaAnilistService {
                     None
                 }
             });
-            let mut related = vec![];
+            let mut related_metadata = vec![];
             details
                 .character_media
                 .unwrap()
@@ -359,7 +360,7 @@ impl MediaProvider for NonMediaAnilistService {
                     );
                     for character in characters {
                         if let Some(character) = character.and_then(|c| c.name) {
-                            related.push(MetadataPersonRelated {
+                            related_metadata.push(MetadataPersonRelated {
                                 character: character.full,
                                 role: "Voicing".to_owned(),
                                 metadata: PartialMetadataWithoutId {
@@ -378,43 +379,37 @@ impl MediaProvider for NonMediaAnilistService {
                         }
                     }
                 });
-            related.extend(
-                details
-                    .staff_media
-                    .unwrap()
-                    .edges
-                    .unwrap()
-                    .into_iter()
-                    .map(|r| {
-                        let edge = r.unwrap();
-                        let data = edge.node.unwrap();
-                        let title = data.title.unwrap();
-                        let title = get_in_preferred_language(
-                            title.native,
-                            title.english,
-                            title.romaji,
-                            &self.base.preferred_language,
-                        );
-                        MetadataPersonRelated {
-                            role: edge.staff_role.unwrap_or_else(|| "Production".to_owned()),
-                            metadata: PartialMetadataWithoutId {
-                                title,
-                                source: MediaSource::Anilist,
-                                identifier: data.id.to_string(),
-                                image: data.cover_image.unwrap().extra_large,
-                                lot: match data.type_.unwrap() {
-                                    staff_query::MediaType::ANIME => MediaLot::Anime,
-                                    staff_query::MediaType::MANGA => MediaLot::Manga,
-                                    staff_query::MediaType::Other(_) => unreachable!(),
-                                },
-                                ..Default::default()
+            related_metadata.extend(details.staff_media.unwrap().edges.unwrap().into_iter().map(
+                |r| {
+                    let edge = r.unwrap();
+                    let data = edge.node.unwrap();
+                    let title = data.title.unwrap();
+                    let title = get_in_preferred_language(
+                        title.native,
+                        title.english,
+                        title.romaji,
+                        &self.base.preferred_language,
+                    );
+                    MetadataPersonRelated {
+                        role: edge.staff_role.unwrap_or_else(|| "Production".to_owned()),
+                        metadata: PartialMetadataWithoutId {
+                            title,
+                            source: MediaSource::Anilist,
+                            identifier: data.id.to_string(),
+                            image: data.cover_image.unwrap().extra_large,
+                            lot: match data.type_.unwrap() {
+                                staff_query::MediaType::ANIME => MediaLot::Anime,
+                                staff_query::MediaType::MANGA => MediaLot::Manga,
+                                staff_query::MediaType::Other(_) => unreachable!(),
                             },
                             ..Default::default()
-                        }
-                    }),
-            );
-            MetadataPerson {
-                related,
+                        },
+                        ..Default::default()
+                    }
+                },
+            ));
+            PersonDetails {
+                related_metadata,
                 death_date,
                 birth_date,
                 images: Some(images),
diff --git a/crates/providers/src/audible.rs b/crates/providers/src/audible.rs
index c4571b3355..3d135076ff 100644
--- a/crates/providers/src/audible.rs
+++ b/crates/providers/src/audible.rs
@@ -5,13 +5,13 @@ use common_models::{NamedObject, PersonSourceSpecifics, SearchDetails};
 use common_utils::{convert_date_to_year, convert_string_to_date, PAGE_SIZE};
 use convert_case::{Case, Casing};
 use database_models::metadata_group::MetadataGroupWithoutId;
-use dependent_models::{PeopleSearchResponse, SearchResults};
+use dependent_models::{PersonDetails, PeopleSearchResponse, SearchResults};
 use educe::Educe;
 use enum_models::{MediaLot, MediaSource};
 use itertools::Itertools;
 use media_models::{
     AudioBookSpecifics, CommitMediaInput, MetadataDetails, MetadataFreeCreator,
-    MetadataImageForMediaDetails, MetadataPerson, MetadataSearchItem, PartialMetadataPerson,
+    MetadataImageForMediaDetails, MetadataSearchItem, PartialMetadataPerson,
     PartialMetadataWithoutId, PeopleSearchItem, UniqueMediaIdentifier,
 };
 use paginate::Pages;
@@ -213,7 +213,7 @@ impl MediaProvider for AudibleService {
         &self,
         identity: &str,
         _source_specifics: &Option<PersonSourceSpecifics>,
-    ) -> Result<MetadataPerson> {
+    ) -> Result<PersonDetails> {
         let client = Client::new();
         let data: AudnexResponse = client
             .get(format!("{}/authors/{}", AUDNEX_URL, identity))
@@ -225,7 +225,7 @@ impl MediaProvider for AudibleService {
             .await
             .map_err(|e| anyhow!(e))?;
         let name = data.name;
-        Ok(MetadataPerson {
+        Ok(PersonDetails {
             name: name.clone(),
             identifier: data.asin,
             source: MediaSource::Audible,
diff --git a/crates/providers/src/igdb.rs b/crates/providers/src/igdb.rs
index f25717141f..034ebd0e8b 100644
--- a/crates/providers/src/igdb.rs
+++ b/crates/providers/src/igdb.rs
@@ -10,16 +10,15 @@ use common_models::{
 use common_utils::{ryot_log, PAGE_SIZE};
 use database_models::metadata_group::MetadataGroupWithoutId;
 use dependent_models::{
-    ApplicationCacheValue, IgdbSettings, MetadataGroupSearchResponse, PeopleSearchResponse,
-    SearchResults,
+    ApplicationCacheValue, IgdbSettings, MetadataGroupSearchResponse, PersonDetails,
+    MetadataPersonRelated, PeopleSearchResponse, SearchResults,
 };
 use enum_models::{MediaLot, MediaSource};
 use itertools::Itertools;
 use media_models::{
     CommitMediaInput, MetadataDetails, MetadataGroupSearchItem, MetadataImageForMediaDetails,
-    MetadataPerson, MetadataPersonRelated, MetadataSearchItem, MetadataVideo, MetadataVideoSource,
-    PartialMetadataPerson, PartialMetadataWithoutId, PeopleSearchItem, UniqueMediaIdentifier,
-    VideoGameSpecifics,
+    MetadataSearchItem, MetadataVideo, MetadataVideoSource, PartialMetadataPerson,
+    PartialMetadataWithoutId, PeopleSearchItem, UniqueMediaIdentifier, VideoGameSpecifics,
 };
 use reqwest::{
     header::{HeaderName, HeaderValue, AUTHORIZATION},
@@ -332,7 +331,7 @@ offset: {offset};
         &self,
         identity: &str,
         _source_specifics: &Option<PersonSourceSpecifics>,
-    ) -> Result<MetadataPerson> {
+    ) -> Result<PersonDetails> {
         let client = self.get_client_config().await?;
         let req_body = format!(
             r#"
@@ -353,7 +352,7 @@ where id = {id};
             .pop()
             .map(|ic| ic.company)
             .ok_or_else(|| anyhow!("No data"))?;
-        let mut related = detail
+        let mut related_metadata = detail
             .published
             .unwrap_or_default()
             .into_iter()
@@ -373,7 +372,7 @@ where id = {id};
                 }
             })
             .collect_vec();
-        related.extend(detail.developed.unwrap_or_default().into_iter().map(|r| {
+        related_metadata.extend(detail.developed.unwrap_or_default().into_iter().map(|r| {
             let image = r.cover.map(|a| self.get_cover_image_url(a.image_id));
             MetadataPersonRelated {
                 role: "Development".to_owned(),
@@ -389,8 +388,8 @@ where id = {id};
             }
         }));
         let name = detail.name;
-        Ok(MetadataPerson {
-            related,
+        Ok(PersonDetails {
+            related_metadata,
             name: name.clone(),
             source: MediaSource::Igdb,
             description: detail.description,
diff --git a/crates/providers/src/manga_updates.rs b/crates/providers/src/manga_updates.rs
index 72b5e70173..fd36e7dc19 100644
--- a/crates/providers/src/manga_updates.rs
+++ b/crates/providers/src/manga_updates.rs
@@ -4,13 +4,14 @@ use async_trait::async_trait;
 use chrono::NaiveDate;
 use common_models::{PersonSourceSpecifics, SearchDetails};
 use common_utils::PAGE_SIZE;
-use dependent_models::{PeopleSearchResponse, SearchResults};
+use dependent_models::{
+    PersonDetails, MetadataPersonRelated, PeopleSearchResponse, SearchResults,
+};
 use enum_models::{MediaLot, MediaSource};
 use itertools::Itertools;
 use media_models::{
-    MangaSpecifics, MetadataDetails, MetadataImageForMediaDetails, MetadataPerson,
-    MetadataPersonRelated, MetadataSearchItem, PartialMetadataPerson, PartialMetadataWithoutId,
-    PeopleSearchItem,
+    MangaSpecifics, MetadataDetails, MetadataImageForMediaDetails, MetadataSearchItem,
+    PartialMetadataPerson, PartialMetadataWithoutId, PeopleSearchItem,
 };
 use reqwest::Client;
 use rust_decimal::Decimal;
@@ -203,7 +204,7 @@ impl MediaProvider for MangaUpdatesService {
         &self,
         identity: &str,
         _source_specifics: &Option<PersonSourceSpecifics>,
-    ) -> Result<MetadataPerson> {
+    ) -> Result<PersonDetails> {
         let data: ItemAuthor = self
             .client
             .get(format!("{}/authors/{}", URL, identity))
@@ -223,7 +224,7 @@ impl MediaProvider for MangaUpdatesService {
             .json()
             .await
             .map_err(|e| anyhow!(e))?;
-        let related = related_data
+        let related_metadata = related_data
             .series_list
             .into_iter()
             .map(|r| MetadataPersonRelated {
@@ -238,8 +239,8 @@ impl MediaProvider for MangaUpdatesService {
                 ..Default::default()
             })
             .collect_vec();
-        let resp = MetadataPerson {
-            related,
+        let resp = PersonDetails {
+            related_metadata,
             gender: data.gender,
             place: data.birthplace,
             name: data.name.unwrap(),
diff --git a/crates/providers/src/openlibrary.rs b/crates/providers/src/openlibrary.rs
index 1d4ef93d47..a1fe684306 100644
--- a/crates/providers/src/openlibrary.rs
+++ b/crates/providers/src/openlibrary.rs
@@ -5,13 +5,12 @@ use chrono::{Datelike, NaiveDate};
 use common_models::{PersonSourceSpecifics, SearchDetails};
 use common_utils::{ryot_log, PAGE_SIZE};
 use convert_case::{Case, Casing};
-use dependent_models::{PeopleSearchResponse, SearchResults};
+use dependent_models::{MetadataPersonRelated, PeopleSearchResponse, PersonDetails, SearchResults};
 use enum_models::{MediaLot, MediaSource};
 use itertools::Itertools;
 use media_models::{
-    BookSpecifics, MetadataDetails, MetadataImageForMediaDetails, MetadataPerson,
-    MetadataPersonRelated, MetadataSearchItem, PartialMetadataPerson, PartialMetadataWithoutId,
-    PeopleSearchItem,
+    BookSpecifics, MetadataDetails, MetadataImageForMediaDetails, MetadataSearchItem,
+    PartialMetadataPerson, PartialMetadataWithoutId, PeopleSearchItem,
 };
 use reqwest::Client;
 use scraper::{Html, Selector};
@@ -207,7 +206,7 @@ impl MediaProvider for OpenlibraryService {
         &self,
         identifier: &str,
         _source_specifics: &Option<PersonSourceSpecifics>,
-    ) -> Result<MetadataPerson> {
+    ) -> Result<PersonDetails> {
         let rsp = self
             .client
             .get(format!("{}/authors/{}.json", URL, identifier))
@@ -238,7 +237,7 @@ impl MediaProvider for OpenlibraryService {
             .json()
             .await
             .map_err(|e| anyhow!(e))?;
-        let mut related = vec![];
+        let mut related_metadata = vec![];
         for entry in author_works.entries.unwrap_or_default() {
             if let Some(title) = entry.title {
                 let image = entry
@@ -250,7 +249,7 @@ impl MediaProvider for OpenlibraryService {
                     .collect_vec()
                     .first()
                     .cloned();
-                related.push(MetadataPersonRelated {
+                related_metadata.push(MetadataPersonRelated {
                     role: "Author".to_owned(),
                     metadata: PartialMetadataWithoutId {
                         title,
@@ -264,11 +263,11 @@ impl MediaProvider for OpenlibraryService {
                 })
             }
         }
-        ryot_log!(debug, "Found {} related works.", related.len());
+        ryot_log!(debug, "Found {} related works.", related_metadata.len());
         let name = data.name;
-        Ok(MetadataPerson {
-            related,
+        Ok(PersonDetails {
             description,
+            related_metadata,
             name: name.clone(),
             images: Some(images),
             identifier: identifier.clone(),
diff --git a/crates/providers/src/tmdb.rs b/crates/providers/src/tmdb.rs
index 217b2e04ae..09eeb4f4fe 100644
--- a/crates/providers/src/tmdb.rs
+++ b/crates/providers/src/tmdb.rs
@@ -13,18 +13,17 @@ use common_models::{
 use common_utils::{convert_date_to_year, convert_string_to_date, SHOW_SPECIAL_SEASON_NAMES};
 use database_models::metadata_group::MetadataGroupWithoutId;
 use dependent_models::{
-    ApplicationCacheValue, MetadataGroupSearchResponse, PeopleSearchResponse, SearchResults,
-    TmdbLanguage, TmdbSettings,
+    ApplicationCacheValue, MetadataGroupSearchResponse, MetadataPersonRelated,
+    PeopleSearchResponse, PersonDetails, SearchResults, TmdbLanguage, TmdbSettings,
 };
 use enum_models::{MediaLot, MediaSource};
 use hashbag::HashBag;
 use itertools::Itertools;
 use media_models::{
     CommitMediaInput, MetadataDetails, MetadataExternalIdentifiers, MetadataGroupSearchItem,
-    MetadataImage, MetadataImageForMediaDetails, MetadataPerson, MetadataPersonRelated,
-    MetadataSearchItem, MetadataVideo, MetadataVideoSource, MovieSpecifics, PartialMetadataPerson,
-    PartialMetadataWithoutId, PeopleSearchItem, ShowEpisode, ShowSeason, ShowSpecifics,
-    UniqueMediaIdentifier, WatchProvider,
+    MetadataImage, MetadataImageForMediaDetails, MetadataSearchItem, MetadataVideo,
+    MetadataVideoSource, MovieSpecifics, PartialMetadataPerson, PartialMetadataWithoutId,
+    PeopleSearchItem, ShowEpisode, ShowSeason, ShowSpecifics, UniqueMediaIdentifier, WatchProvider,
 };
 use reqwest::{
     header::{HeaderValue, AUTHORIZATION},
@@ -438,7 +437,7 @@ impl MediaProvider for NonMediaTmdbService {
         &self,
         identifier: &str,
         source_specifics: &Option<PersonSourceSpecifics>,
-    ) -> Result<MetadataPerson> {
+    ) -> Result<PersonDetails> {
         let type_ = match source_specifics {
             Some(PersonSourceSpecifics {
                 is_tmdb_company: Some(true),
@@ -467,7 +466,7 @@ impl MediaProvider for NonMediaTmdbService {
             .map(|p| self.base.get_image_url(p))
             .collect();
         let description = details.description.or(details.biography);
-        let mut related = vec![];
+        let mut related_metadata = vec![];
         if type_ == "person" {
             let cred_det: TmdbCreditsResponse = self
                 .base
@@ -494,7 +493,7 @@ impl MediaProvider for NonMediaTmdbService {
                     },
                     ..Default::default()
                 };
-                related.push(MetadataPersonRelated {
+                related_metadata.push(MetadataPersonRelated {
                     role,
                     metadata,
                     character: media.character,
@@ -515,21 +514,23 @@ impl MediaProvider for NonMediaTmdbService {
                         .json()
                         .await
                         .map_err(|e| anyhow!(e))?;
-                    related.extend(cred_det.results.into_iter().map(|m| MetadataPersonRelated {
-                        role: "Production Company".to_owned(),
-                        metadata: PartialMetadataWithoutId {
-                            source: MediaSource::Tmdb,
-                            identifier: m.id.to_string(),
-                            title: m.title.unwrap_or_default(),
-                            image: m.poster_path.map(|p| self.base.get_image_url(p)),
-                            lot: match m_typ {
-                                "movie" => MediaLot::Movie,
-                                "tv" => MediaLot::Show,
-                                _ => unreachable!(),
+                    related_metadata.extend(cred_det.results.into_iter().map(|m| {
+                        MetadataPersonRelated {
+                            role: "Production Company".to_owned(),
+                            metadata: PartialMetadataWithoutId {
+                                source: MediaSource::Tmdb,
+                                identifier: m.id.to_string(),
+                                title: m.title.unwrap_or_default(),
+                                image: m.poster_path.map(|p| self.base.get_image_url(p)),
+                                lot: match m_typ {
+                                    "movie" => MediaLot::Movie,
+                                    "tv" => MediaLot::Show,
+                                    _ => unreachable!(),
+                                },
+                                ..Default::default()
                             },
                             ..Default::default()
-                        },
-                        ..Default::default()
+                        }
                     }));
                     if cred_det.page == cred_det.total_pages {
                         break;
@@ -538,8 +539,8 @@ impl MediaProvider for NonMediaTmdbService {
             }
         }
         let name = details.name;
-        let resp = MetadataPerson {
-            related,
+        let resp = PersonDetails {
+            related_metadata,
             name: name.clone(),
             images: Some(images),
             source: MediaSource::Tmdb,
@@ -549,17 +550,18 @@ impl MediaProvider for NonMediaTmdbService {
             identifier: details.id.to_string(),
             source_specifics: source_specifics.to_owned(),
             place: details.origin_country.or(details.place_of_birth),
+            description: description.and_then(|s| if s.as_str() == "" { None } else { Some(s) }),
             source_url: Some(format!(
                 "https://www.themoviedb.org/person/{}-{}",
                 identifier, name
             )),
-            description: description.and_then(|s| if s.as_str() == "" { None } else { Some(s) }),
             gender: details.gender.and_then(|g| match g {
                 1 => Some("Female".to_owned()),
                 2 => Some("Male".to_owned()),
                 3 => Some("Non-Binary".to_owned()),
                 _ => None,
             }),
+            ..Default::default()
         };
         Ok(resp)
     }
diff --git a/crates/providers/src/vndb.rs b/crates/providers/src/vndb.rs
index 02e1a2a666..1d902e213f 100644
--- a/crates/providers/src/vndb.rs
+++ b/crates/providers/src/vndb.rs
@@ -3,12 +3,12 @@ use application_utils::get_base_http_client;
 use async_trait::async_trait;
 use common_models::{NamedObject, PersonSourceSpecifics, SearchDetails};
 use common_utils::{convert_date_to_year, convert_string_to_date, PAGE_SIZE};
-use dependent_models::{PeopleSearchResponse, SearchResults};
+use dependent_models::{PersonDetails, PeopleSearchResponse, SearchResults};
 use enum_models::{MediaLot, MediaSource};
 use itertools::Itertools;
 use media_models::{
-    MetadataDetails, MetadataImageForMediaDetails, MetadataPerson, MetadataSearchItem,
-    PartialMetadataPerson, PeopleSearchItem, VisualNovelSpecifics,
+    MetadataDetails, MetadataImageForMediaDetails, MetadataSearchItem, PartialMetadataPerson,
+    PeopleSearchItem, VisualNovelSpecifics,
 };
 use reqwest::Client;
 use rust_decimal::Decimal;
@@ -122,7 +122,7 @@ impl MediaProvider for VndbService {
         &self,
         identifier: &str,
         _source_specifics: &Option<PersonSourceSpecifics>,
-    ) -> Result<MetadataPerson> {
+    ) -> Result<PersonDetails> {
         let rsp = self
             .client
             .post(format!("{}/producer", URL))
@@ -136,7 +136,7 @@ impl MediaProvider for VndbService {
             .map_err(|e| anyhow!(e))?;
         let data: SearchResponse = rsp.json().await.map_err(|e| anyhow!(e))?;
         let item = data.results.unwrap_or_default().pop().unwrap();
-        Ok(MetadataPerson {
+        Ok(PersonDetails {
             identifier: item.id,
             name: item.title.unwrap(),
             source: MediaSource::Vndb,
diff --git a/crates/providers/src/youtube_music.rs b/crates/providers/src/youtube_music.rs
index 2d4f355bea..213cb64a86 100644
--- a/crates/providers/src/youtube_music.rs
+++ b/crates/providers/src/youtube_music.rs
@@ -3,14 +3,16 @@ use async_trait::async_trait;
 use common_models::{PersonSourceSpecifics, SearchDetails, StoredUrl};
 use common_utils::TEMP_DIR;
 use database_models::metadata_group::MetadataGroupWithoutId;
-use dependent_models::{MetadataGroupSearchResponse, PeopleSearchResponse, SearchResults};
+use dependent_models::{
+    MetadataGroupPersonRelated, MetadataGroupSearchResponse, MetadataPersonRelated,
+    PeopleSearchResponse, PersonDetails, SearchResults,
+};
 use enum_models::{MediaLot, MediaSource};
 use itertools::Itertools;
 use media_models::{
     CommitMediaInput, MetadataDetails, MetadataGroupSearchItem, MetadataImage,
-    MetadataImageForMediaDetails, MetadataPerson, MetadataPersonRelated, MetadataSearchItem,
-    MusicSpecifics, PartialMetadataPerson, PartialMetadataWithoutId, PeopleSearchItem,
-    UniqueMediaIdentifier,
+    MetadataImageForMediaDetails, MetadataSearchItem, MusicSpecifics, PartialMetadataPerson,
+    PartialMetadataWithoutId, PeopleSearchItem, UniqueMediaIdentifier,
 };
 use rustypipe::{
     client::{RustyPipe, RustyPipeQuery},
@@ -125,7 +127,7 @@ impl MediaProvider for YoutubeMusicService {
         let results = self.client.music_search_tracks(query).await?;
         let data = SearchResults {
             details: SearchDetails {
-                total: 1,
+                total: 100,
                 ..Default::default()
             },
             items: results
@@ -194,7 +196,7 @@ impl MediaProvider for YoutubeMusicService {
         let data = self.client.music_search_albums(query).await?;
         Ok(SearchResults {
             details: SearchDetails {
-                total: 1,
+                total: 100,
                 ..Default::default()
             },
             items: data
@@ -215,34 +217,58 @@ impl MediaProvider for YoutubeMusicService {
         &self,
         identifier: &str,
         _source_specifics: &Option<PersonSourceSpecifics>,
-    ) -> Result<MetadataPerson> {
-        let data = self.client.music_artist(identifier, false).await?;
-        let related = if let Some(playlist_id) = data.tracks_playlist_id {
-            let items = self.client.music_playlist(playlist_id).await?;
-            items
-                .tracks
-                .items
-                .into_iter()
-                .map(|t| MetadataPersonRelated {
-                    role: "Artist".to_string(),
-                    metadata: PartialMetadataWithoutId {
-                        title: t.name,
-                        identifier: t.id,
-                        lot: MediaLot::Music,
-                        source: MediaSource::YoutubeMusic,
-                        image: self.largest_image(&t.cover).map(|t| t.url.to_owned()),
+    ) -> Result<PersonDetails> {
+        let data = self.client.music_artist(identifier, true).await?;
+        let related_metadata = match data.tracks_playlist_id {
+            None => vec![],
+            Some(playlist_id) => {
+                let items = self.client.music_playlist(playlist_id).await?;
+                items
+                    .tracks
+                    .items
+                    .into_iter()
+                    .map(|t| MetadataPersonRelated {
+                        role: "Artist".to_string(),
+                        metadata: PartialMetadataWithoutId {
+                            title: t.name,
+                            identifier: t.id,
+                            lot: MediaLot::Music,
+                            source: MediaSource::YoutubeMusic,
+                            image: self.largest_image(&t.cover).map(|t| t.url.to_owned()),
+                            ..Default::default()
+                        },
                         ..Default::default()
-                    },
-                    ..Default::default()
-                })
-                .collect()
-        } else {
-            vec![]
+                    })
+                    .collect()
+            }
         };
+        let related_metadata_groups = data
+            .albums
+            .into_iter()
+            .map(|a| MetadataGroupPersonRelated {
+                role: "Artist".to_string(),
+                metadata_group: MetadataGroupWithoutId {
+                    title: a.name,
+                    lot: MediaLot::Music,
+                    identifier: a.id.clone(),
+                    source: MediaSource::YoutubeMusic,
+                    images: Some(
+                        self.order_images_by_size(&a.cover)
+                            .into_iter()
+                            .map(|c| MetadataImage {
+                                url: StoredUrl::Url(c.url),
+                            })
+                            .collect(),
+                    ),
+                    ..Default::default()
+                },
+            })
+            .collect();
         let identifier = data.id;
-        Ok(MetadataPerson {
-            related,
+        Ok(PersonDetails {
             name: data.name,
+            related_metadata,
+            related_metadata_groups,
             description: data.description,
             identifier: identifier.clone(),
             source: MediaSource::YoutubeMusic,
@@ -267,7 +293,7 @@ impl MediaProvider for YoutubeMusicService {
         let data = self.client.music_search_artists(query).await?;
         Ok(SearchResults {
             details: SearchDetails {
-                total: 1,
+                total: 100,
                 ..Default::default()
             },
             items: data
diff --git a/crates/resolvers/miscellaneous/src/lib.rs b/crates/resolvers/miscellaneous/src/lib.rs
index eb91dd741f..0dc408cf3b 100644
--- a/crates/resolvers/miscellaneous/src/lib.rs
+++ b/crates/resolvers/miscellaneous/src/lib.rs
@@ -6,8 +6,8 @@ use common_models::{
     StringIdObject,
 };
 use dependent_models::{
-    CoreDetails, GenreDetails, MetadataGroupDetails, MetadataGroupSearchResponse,
-    MetadataSearchResponse, PeopleSearchResponse, PersonDetails, SearchResults,
+    CoreDetails, GenreDetails, GraphqlPersonDetails, MetadataGroupDetails,
+    MetadataGroupSearchResponse, MetadataSearchResponse, PeopleSearchResponse, SearchResults,
     UserMetadataDetails, UserMetadataGroupDetails, UserPersonDetails,
 };
 use media_models::{
@@ -58,7 +58,7 @@ impl MiscellaneousQuery {
         &self,
         gql_ctx: &Context<'_>,
         person_id: String,
-    ) -> Result<PersonDetails> {
+    ) -> Result<GraphqlPersonDetails> {
         let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
         service.person_details(person_id).await
     }
diff --git a/crates/services/miscellaneous/src/lib.rs b/crates/services/miscellaneous/src/lib.rs
index 650cd96dc1..869ea10625 100644
--- a/crates/services/miscellaneous/src/lib.rs
+++ b/crates/services/miscellaneous/src/lib.rs
@@ -23,14 +23,14 @@ use common_utils::{
 use database_models::{
     access_link, application_cache, calendar_event, collection, collection_to_entity,
     functions::{associate_user_with_entity, get_user_to_entity_association},
-    genre, import_report, metadata, metadata_group, metadata_to_genre, metadata_to_metadata,
-    metadata_to_metadata_group, metadata_to_person, monitored_entity, notification_platform,
-    person,
+    genre, import_report, metadata, metadata_group, metadata_group_to_person, metadata_to_genre,
+    metadata_to_metadata, metadata_to_metadata_group, metadata_to_person, monitored_entity,
+    notification_platform, person,
     prelude::{
         AccessLink, ApplicationCache, CalendarEvent, Collection, CollectionToEntity, Genre,
-        ImportReport, Metadata, MetadataGroup, MetadataToGenre, MetadataToMetadata,
-        MetadataToMetadataGroup, MetadataToPerson, MonitoredEntity, NotificationPlatform, Person,
-        Review, Seen, User, UserNotification, UserToEntity,
+        ImportReport, Metadata, MetadataGroup, MetadataGroupToPerson, MetadataToGenre,
+        MetadataToMetadata, MetadataToMetadataGroup, MetadataToPerson, MonitoredEntity,
+        NotificationPlatform, Person, Review, Seen, User, UserNotification, UserToEntity,
     },
     review, seen, user, user_notification, user_to_entity,
 };
@@ -40,9 +40,10 @@ use database_utils::{
     revoke_access_link, user_by_id,
 };
 use dependent_models::{
-    ApplicationCacheValue, CoreDetails, GenreDetails, MetadataBaseData, MetadataGroupDetails,
-    MetadataGroupSearchResponse, MetadataSearchResponse, PeopleSearchResponse, PersonDetails,
-    SearchResults, UserMetadataDetails, UserMetadataGroupDetails, UserPersonDetails,
+    ApplicationCacheValue, CoreDetails, GenreDetails, GraphqlPersonDetails, MetadataBaseData,
+    MetadataGroupDetails, MetadataGroupSearchResponse, MetadataSearchResponse,
+    PeopleSearchResponse, SearchResults, UserMetadataDetails, UserMetadataGroupDetails,
+    UserPersonDetails,
 };
 use dependent_utils::{
     add_entity_to_collection, change_metadata_associations, commit_metadata, commit_metadata_group,
@@ -393,27 +394,22 @@ ORDER BY RANDOM() LIMIT 10;
             genres,
             suggestions,
         } = self.generic_metadata(metadata_id).await?;
-        let group = {
-            let association = MetadataToMetadataGroup::find()
-                .filter(metadata_to_metadata_group::Column::MetadataId.eq(metadata_id))
-                .one(&self.0.db)
-                .await?;
-            match association {
-                None => None,
-                Some(a) => {
-                    let grp = a
-                        .find_related(MetadataGroup)
-                        .one(&self.0.db)
-                        .await?
-                        .unwrap();
-                    Some(GraphqlMetadataGroup {
-                        id: grp.id,
-                        name: grp.title,
-                        part: a.part,
-                    })
-                }
-            }
-        };
+
+        let mut group = vec![];
+        let associations = MetadataToMetadataGroup::find()
+            .filter(metadata_to_metadata_group::Column::MetadataId.eq(metadata_id))
+            .find_also_related(MetadataGroup)
+            .all(&self.0.db)
+            .await?;
+        for association in associations {
+            let grp = association.1.unwrap();
+            group.push(GraphqlMetadataGroup {
+                id: grp.id,
+                name: grp.title,
+                part: association.0.part,
+            });
+        }
+
         let watch_providers = model.watch_providers.unwrap_or_default();
 
         let resp = GraphqlMetadataDetails {
@@ -2092,32 +2088,30 @@ ORDER BY RANDOM() LIMIT 10;
         })
     }
 
-    pub async fn person_details(&self, person_id: String) -> Result<PersonDetails> {
+    pub async fn person_details(&self, person_id: String) -> Result<GraphqlPersonDetails> {
         let mut details = Person::find_by_id(person_id.clone())
             .one(&self.0.db)
             .await?
             .unwrap();
         details.display_images =
             metadata_images_as_urls(&details.images, &self.0.file_storage_service).await;
-        let associations = MetadataToPerson::find()
-            .filter(metadata_to_person::Column::PersonId.eq(person_id))
+        let metadata_associations = MetadataToPerson::find()
+            .filter(metadata_to_person::Column::PersonId.eq(&person_id))
             .order_by_asc(metadata_to_person::Column::Index)
             .all(&self.0.db)
             .await?;
-        let mut contents: HashMap<_, Vec<_>> = HashMap::new();
-        for assoc in associations {
+        let mut metadata_contents: HashMap<_, Vec<_>> = HashMap::new();
+        for assoc in metadata_associations {
             let to_push = PersonDetailsItemWithCharacter {
                 character: assoc.character,
-                metadata_id: assoc.metadata_id,
+                entity_id: assoc.metadata_id,
             };
-            contents
+            metadata_contents
                 .entry(assoc.role)
-                .and_modify(|e| {
-                    e.push(to_push.clone());
-                })
+                .and_modify(|e| e.push(to_push.clone()))
                 .or_insert(vec![to_push]);
         }
-        let contents = contents
+        let associated_metadata = metadata_contents
             .into_iter()
             .map(|(name, items)| PersonDetailsGroupedByRole {
                 count: items.len(),
@@ -2126,7 +2120,36 @@ ORDER BY RANDOM() LIMIT 10;
             })
             .sorted_by_key(|f| Reverse(f.count))
             .collect_vec();
-        Ok(PersonDetails { details, contents })
+        let associated_metadata_groups = MetadataGroupToPerson::find()
+            .filter(metadata_group_to_person::Column::PersonId.eq(person_id))
+            .order_by_asc(metadata_group_to_person::Column::Index)
+            .all(&self.0.db)
+            .await?;
+        let mut metadata_group_contents: HashMap<_, Vec<_>> = HashMap::new();
+        for assoc in associated_metadata_groups {
+            let to_push = PersonDetailsItemWithCharacter {
+                entity_id: assoc.metadata_group_id,
+                ..Default::default()
+            };
+            metadata_group_contents
+                .entry(assoc.role)
+                .and_modify(|e| e.push(to_push.clone()))
+                .or_insert(vec![to_push]);
+        }
+        let associated_metadata_groups = metadata_group_contents
+            .into_iter()
+            .map(|(name, items)| PersonDetailsGroupedByRole {
+                count: items.len(),
+                name,
+                items,
+            })
+            .sorted_by_key(|f| Reverse(f.count))
+            .collect_vec();
+        Ok(GraphqlPersonDetails {
+            details,
+            associated_metadata,
+            associated_metadata_groups,
+        })
     }
 
     pub async fn genre_details(&self, input: GenreDetailsInput) -> Result<GenreDetails> {
@@ -2504,7 +2527,7 @@ ORDER BY RANDOM() LIMIT 10;
                     .collect()
             })
             .filter(|i: &Vec<MetadataImage>| !i.is_empty());
-        let mut default_state_changes = person.clone().state_changes.unwrap_or_default();
+        let mut current_state_changes = person.clone().state_changes.unwrap_or_default();
         let mut to_update_person: person::ActiveModel = person.clone().into();
         to_update_person.last_updated_on = ActiveValue::Set(Utc::now());
         to_update_person.description = ActiveValue::Set(provider_person.description);
@@ -2517,7 +2540,7 @@ ORDER BY RANDOM() LIMIT 10;
         to_update_person.images = ActiveValue::Set(images);
         to_update_person.is_partial = ActiveValue::Set(Some(false));
         to_update_person.name = ActiveValue::Set(provider_person.name);
-        for data in provider_person.related.clone() {
+        for data in provider_person.related_metadata.clone() {
             let title = data.metadata.title.clone();
             let pm = create_partial_metadata(data.metadata, &self.0.db).await?;
             let already_intermediate = MetadataToPerson::find()
@@ -2544,18 +2567,84 @@ ORDER BY RANDOM() LIMIT 10;
                     identifier: pm.identifier.clone(),
                 },
             };
-            if !default_state_changes.media_associated.contains(&search_for) {
+            if !current_state_changes
+                .metadata_associated
+                .contains(&search_for)
+            {
                 notifications.push((
                     format!(
                         "{} has been associated with {} as {}",
                         person.name, title, data.role
                     ),
-                    MediaStateChanged::PersonMediaAssociated,
+                    MediaStateChanged::PersonMetadataAssociated,
+                ));
+                current_state_changes.metadata_associated.insert(search_for);
+            }
+        }
+        for (idx, data) in provider_person.related_metadata_groups.iter().enumerate() {
+            let db_dg = match MetadataGroup::find()
+                .filter(metadata_group::Column::Lot.eq(data.metadata_group.lot))
+                .filter(metadata_group::Column::Source.eq(data.metadata_group.source))
+                .filter(metadata_group::Column::Identifier.eq(&data.metadata_group.identifier))
+                .one(&self.0.db)
+                .await?
+            {
+                Some(m) => m.id,
+                None => {
+                    let m = metadata_group::ActiveModel {
+                        is_partial: ActiveValue::Set(Some(true)),
+                        lot: ActiveValue::Set(data.metadata_group.lot),
+                        source: ActiveValue::Set(data.metadata_group.source),
+                        title: ActiveValue::Set(data.metadata_group.title.clone()),
+                        identifier: ActiveValue::Set(data.metadata_group.identifier.clone()),
+                        images: ActiveValue::Set(
+                            data.metadata_group.images.clone().filter(|i| !i.is_empty()),
+                        ),
+                        ..Default::default()
+                    };
+                    m.insert(&self.0.db).await?.id
+                }
+            };
+            let already_intermediate = MetadataGroupToPerson::find()
+                .filter(metadata_group_to_person::Column::Role.eq(&data.role))
+                .filter(metadata_group_to_person::Column::PersonId.eq(&person_id))
+                .filter(metadata_group_to_person::Column::MetadataGroupId.eq(&db_dg))
+                .one(&self.0.db)
+                .await?;
+            if already_intermediate.is_none() {
+                let intermediate = metadata_group_to_person::ActiveModel {
+                    role: ActiveValue::Set(data.role.clone()),
+                    metadata_group_id: ActiveValue::Set(db_dg),
+                    index: ActiveValue::Set(idx.try_into().unwrap()),
+                    person_id: ActiveValue::Set(person_id.to_owned()),
+                };
+                intermediate.insert(&self.0.db).await?;
+            }
+            let search_for = MediaAssociatedPersonStateChanges {
+                role: data.role.clone(),
+                media: UniqueMediaIdentifier {
+                    lot: data.metadata_group.lot,
+                    source: data.metadata_group.source,
+                    identifier: data.metadata_group.identifier.clone(),
+                },
+            };
+            if !current_state_changes
+                .metadata_groups_associated
+                .contains(&search_for)
+            {
+                notifications.push((
+                    format!(
+                        "{} has been associated with {} as {}",
+                        person.name, data.metadata_group.title, data.role
+                    ),
+                    MediaStateChanged::PersonMetadataGroupAssociated,
                 ));
-                default_state_changes.media_associated.insert(search_for);
+                current_state_changes
+                    .metadata_groups_associated
+                    .insert(search_for);
             }
         }
-        to_update_person.state_changes = ActiveValue::Set(Some(default_state_changes));
+        to_update_person.state_changes = ActiveValue::Set(Some(current_state_changes));
         to_update_person.update(&self.0.db).await.unwrap();
         Ok(notifications)
     }
diff --git a/crates/traits/src/lib.rs b/crates/traits/src/lib.rs
index 988a8d6825..1a7b061d1d 100644
--- a/crates/traits/src/lib.rs
+++ b/crates/traits/src/lib.rs
@@ -8,8 +8,10 @@ use common_models::{BackendError, PersonSourceSpecifics};
 use common_utils::ryot_log;
 use database_models::metadata_group::MetadataGroupWithoutId;
 use database_utils::check_token;
-use dependent_models::{MetadataGroupSearchResponse, PeopleSearchResponse, SearchResults};
-use media_models::{MetadataDetails, MetadataPerson, MetadataSearchItem, PartialMetadataWithoutId};
+use dependent_models::{
+    MetadataGroupSearchResponse, PersonDetails, PeopleSearchResponse, SearchResults,
+};
+use media_models::{MetadataDetails, MetadataSearchItem, PartialMetadataWithoutId};
 use sea_orm::DatabaseConnection;
 
 #[async_trait]
@@ -49,7 +51,7 @@ pub trait MediaProvider {
         &self,
         identifier: &str,
         source_specifics: &Option<PersonSourceSpecifics>,
-    ) -> Result<MetadataPerson> {
+    ) -> Result<PersonDetails> {
         bail!("This provider does not support getting person details")
     }
 
diff --git a/crates/utils/dependent/src/lib.rs b/crates/utils/dependent/src/lib.rs
index 56c7315d0a..82045d3957 100644
--- a/crates/utils/dependent/src/lib.rs
+++ b/crates/utils/dependent/src/lib.rs
@@ -726,7 +726,6 @@ pub async fn commit_metadata_group(
         Some(m) => Ok(m),
         None => {
             let new_group = metadata_group::ActiveModel {
-                parts: ActiveValue::Set(0),
                 title: ActiveValue::Set(input.name),
                 lot: ActiveValue::Set(input.unique.lot),
                 is_partial: ActiveValue::Set(Some(true)),
diff --git a/libs/generated/src/graphql/backend/gql.ts b/libs/generated/src/graphql/backend/gql.ts
index b0fee4cd3b..7c2202c146 100644
--- a/libs/generated/src/graphql/backend/gql.ts
+++ b/libs/generated/src/graphql/backend/gql.ts
@@ -16,14 +16,14 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/
 const documents = {
     "mutation RegisterUser($input: RegisterUserInput!) {\n  registerUser(input: $input) {\n    __typename\n    ... on RegisterError {\n      error\n    }\n    ... on StringIdObject {\n      id\n    }\n  }\n}\n\nmutation LoginUser($input: AuthUserInput!) {\n  loginUser(input: $input) {\n    __typename\n    ... on LoginError {\n      error\n    }\n    ... on LoginResponse {\n      apiKey\n    }\n  }\n}\n\nmutation AddEntityToCollection($input: ChangeCollectionToEntityInput!) {\n  addEntityToCollection(input: $input)\n}\n\nmutation CommitMetadata($input: CommitMediaInput!) {\n  commitMetadata(input: $input) {\n    id\n  }\n}\n\nmutation CommitMetadataGroup($input: CommitMediaInput!) {\n  commitMetadataGroup(input: $input) {\n    id\n  }\n}\n\nmutation CommitPerson($input: CommitPersonInput!) {\n  commitPerson(input: $input) {\n    id\n  }\n}\n\nmutation CreateCustomExercise($input: ExerciseInput!) {\n  createCustomExercise(input: $input)\n}\n\nmutation UpdateCustomExercise($input: UpdateCustomExerciseInput!) {\n  updateCustomExercise(input: $input)\n}\n\nmutation UpdateUserIntegration($input: UpdateUserIntegrationInput!) {\n  updateUserIntegration(input: $input)\n}\n\nmutation CreateCustomMetadata($input: CreateCustomMetadataInput!) {\n  createCustomMetadata(input: $input) {\n    id\n  }\n}\n\nmutation UpdateCustomMetadata($input: UpdateCustomMetadataInput!) {\n  updateCustomMetadata(input: $input)\n}\n\nmutation CreateOrUpdateCollection($input: CreateOrUpdateCollectionInput!) {\n  createOrUpdateCollection(input: $input) {\n    id\n  }\n}\n\nmutation CreateReviewComment($input: CreateReviewCommentInput!) {\n  createReviewComment(input: $input)\n}\n\nmutation CreateUserMeasurement($input: UserMeasurementInput!) {\n  createUserMeasurement(input: $input)\n}\n\nmutation CreateUserNotificationPlatform($input: CreateUserNotificationPlatformInput!) {\n  createUserNotificationPlatform(input: $input)\n}\n\nmutation CreateUserIntegration($input: CreateUserIntegrationInput!) {\n  createUserIntegration(input: $input) {\n    id\n  }\n}\n\nmutation CreateOrUpdateUserWorkout($input: UserWorkoutInput!) {\n  createOrUpdateUserWorkout(input: $input)\n}\n\nmutation CreateOrUpdateUserWorkoutTemplate($input: UserWorkoutInput!) {\n  createOrUpdateUserWorkoutTemplate(input: $input)\n}\n\nmutation DeleteCollection($collectionName: String!) {\n  deleteCollection(collectionName: $collectionName)\n}\n\nmutation DeleteReview($reviewId: String!) {\n  deleteReview(reviewId: $reviewId)\n}\n\nmutation DeleteS3Object($key: String!) {\n  deleteS3Object(key: $key)\n}\n\nmutation DeleteSeenItem($seenId: String!) {\n  deleteSeenItem(seenId: $seenId) {\n    id\n  }\n}\n\nmutation DeleteUser($toDeleteUserId: String!) {\n  deleteUser(toDeleteUserId: $toDeleteUserId)\n}\n\nmutation DeleteUserIntegration($integrationId: String!) {\n  deleteUserIntegration(integrationId: $integrationId)\n}\n\nmutation DeleteUserMeasurement($timestamp: DateTime!) {\n  deleteUserMeasurement(timestamp: $timestamp)\n}\n\nmutation DeleteUserNotificationPlatform($notificationId: String!) {\n  deleteUserNotificationPlatform(notificationId: $notificationId)\n}\n\nmutation DeleteUserWorkout($workoutId: String!) {\n  deleteUserWorkout(workoutId: $workoutId)\n}\n\nmutation DeleteUserWorkoutTemplate($workoutTemplateId: String!) {\n  deleteUserWorkoutTemplate(workoutTemplateId: $workoutTemplateId)\n}\n\nmutation DeployBackgroundJob($jobName: BackgroundJob!) {\n  deployBackgroundJob(jobName: $jobName)\n}\n\nmutation DeployBulkProgressUpdate($input: [ProgressUpdateInput!]!) {\n  deployBulkProgressUpdate(input: $input)\n}\n\nmutation DeployExportJob {\n  deployExportJob\n}\n\nmutation DeployImportJob($input: DeployImportJobInput!) {\n  deployImportJob(input: $input)\n}\n\nmutation DeployUpdateMetadataJob($metadataId: String!) {\n  deployUpdateMetadataJob(metadataId: $metadataId)\n}\n\nmutation DeployUpdatePersonJob($personId: String!) {\n  deployUpdatePersonJob(personId: $personId)\n}\n\nmutation DeployUpdateMetadataGroupJob($metadataGroupId: String!) {\n  deployUpdateMetadataGroupJob(metadataGroupId: $metadataGroupId)\n}\n\nmutation UpdateSeenItem($input: UpdateSeenItemInput!) {\n  updateSeenItem(input: $input)\n}\n\nmutation UpdateUserNotificationPlatform($input: UpdateUserNotificationPlatformInput!) {\n  updateUserNotificationPlatform(input: $input)\n}\n\nmutation UpdateUserWorkoutAttributes($input: UpdateUserWorkoutAttributesInput!) {\n  updateUserWorkoutAttributes(input: $input)\n}\n\nmutation GenerateAuthToken {\n  generateAuthToken\n}\n\nmutation MergeMetadata($mergeFrom: String!, $mergeInto: String!) {\n  mergeMetadata(mergeFrom: $mergeFrom, mergeInto: $mergeInto)\n}\n\nmutation DisassociateMetadata($metadataId: String!) {\n  disassociateMetadata(metadataId: $metadataId)\n}\n\nmutation CreateOrUpdateReview($input: CreateOrUpdateReviewInput!) {\n  createOrUpdateReview(input: $input) {\n    id\n  }\n}\n\nmutation PresignedPutS3Url($input: PresignedPutUrlInput!) {\n  presignedPutS3Url(input: $input) {\n    key\n    uploadUrl\n  }\n}\n\nmutation RemoveEntityFromCollection($input: ChangeCollectionToEntityInput!) {\n  removeEntityFromCollection(input: $input) {\n    id\n  }\n}\n\nmutation TestUserNotificationPlatforms {\n  testUserNotificationPlatforms\n}\n\nmutation UpdateUser($input: UpdateUserInput!) {\n  updateUser(input: $input) {\n    id\n  }\n}\n\nmutation UpdateUserPreference($input: UserPreferencesInput!) {\n  updateUserPreference(input: $input)\n}\n\nmutation CreateAccessLink($input: CreateAccessLinkInput!) {\n  createAccessLink(input: $input) {\n    id\n  }\n}\n\nmutation ProcessAccessLink($input: ProcessAccessLinkInput!) {\n  processAccessLink(input: $input) {\n    __typename\n    ... on ProcessAccessLinkError {\n      error\n    }\n    ... on ProcessAccessLinkResponse {\n      apiKey\n      redirectTo\n      tokenValidForDays\n    }\n  }\n}\n\nmutation RevokeAccessLink($accessLinkId: String!) {\n  revokeAccessLink(accessLinkId: $accessLinkId)\n}\n\nmutation UpdateUserExerciseSettings($input: UpdateUserExerciseSettings!) {\n  updateUserExerciseSettings(input: $input)\n}\n\nmutation MergeExercise($mergeFrom: String!, $mergeInto: String!) {\n  mergeExercise(mergeFrom: $mergeFrom, mergeInto: $mergeInto)\n}\n\nmutation RefreshUserRecommendationsKey {\n  refreshUserRecommendationsKey\n}": types.RegisterUserDocument,
     "query MetadataDetails($metadataId: String!) {\n  metadataDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    source\n    isNsfw\n    isPartial\n    sourceUrl\n    identifier\n    description\n    suggestions\n    publishYear\n    publishDate\n    providerRating\n    createdByUserId\n    productionStatus\n    originalLanguage\n    genres {\n      id\n      name\n    }\n    group {\n      id\n      name\n      part\n    }\n    assets {\n      images\n      videos {\n        videoId\n        source\n      }\n    }\n    creators {\n      name\n      items {\n        id\n        name\n        image\n        character\n      }\n    }\n    watchProviders {\n      name\n      image\n      languages\n    }\n    animeSpecifics {\n      episodes\n    }\n    audioBookSpecifics {\n      runtime\n    }\n    bookSpecifics {\n      pages\n    }\n    movieSpecifics {\n      runtime\n    }\n    mangaSpecifics {\n      volumes\n      chapters\n    }\n    podcastSpecifics {\n      episodes {\n        id\n        title\n        overview\n        thumbnail\n        number\n        runtime\n        publishDate\n      }\n      totalEpisodes\n    }\n    showSpecifics {\n      totalSeasons\n      totalEpisodes\n      runtime\n      seasons {\n        id\n        seasonNumber\n        name\n        overview\n        backdropImages\n        posterImages\n        episodes {\n          id\n          name\n          posterImages\n          episodeNumber\n          publishDate\n          name\n          overview\n          runtime\n        }\n      }\n    }\n    visualNovelSpecifics {\n      length\n    }\n    videoGameSpecifics {\n      platforms\n    }\n    musicSpecifics {\n      duration\n      viewCount\n      byVariousArtists\n    }\n  }\n}": types.MetadataDetailsDocument,
-    "query PersonDetails($personId: String!) {\n  personDetails(personId: $personId) {\n    details {\n      id\n      name\n      source\n      identifier\n      isPartial\n      description\n      birthDate\n      deathDate\n      place\n      website\n      gender\n      displayImages\n      sourceUrl\n    }\n    contents {\n      name\n      count\n      items {\n        character\n        metadataId\n      }\n    }\n  }\n}": types.PersonDetailsDocument,
+    "query PersonDetails($personId: String!) {\n  personDetails(personId: $personId) {\n    associatedMetadata {\n      ...PersonDetailsGroupedByRolePart\n    }\n    associatedMetadataGroups {\n      ...PersonDetailsGroupedByRolePart\n    }\n    details {\n      id\n      name\n      place\n      source\n      gender\n      website\n      deathDate\n      birthDate\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}": types.PersonDetailsDocument,
     "query UserAnalytics($input: UserAnalyticsInput!) {\n  userAnalytics(input: $input) {\n    hours {\n      hour\n      entities {\n        entityId\n        entityLot\n        metadataLot\n      }\n    }\n    activities {\n      groupedBy\n      totalCount\n      totalDuration\n      items {\n        day\n        bookCount\n        showCount\n        totalCount\n        movieCount\n        animeCount\n        mangaCount\n        workoutCount\n        podcastCount\n        totalDuration\n        totalBookPages\n        audioBookCount\n        videoGameCount\n        totalReviewCount\n        visualNovelCount\n        totalWorkoutReps\n        totalShowDuration\n        totalMovieDuration\n        totalMetadataCount\n        totalWorkoutWeight\n        totalPodcastDuration\n        totalWorkoutDistance\n        totalWorkoutRestTime\n        totalWorkoutDuration\n        userMeasurementCount\n        totalVideoGameDuration\n        totalAudioBookDuration\n        totalPersonReviewCount\n        totalVisualNovelDuration\n        totalMetadataReviewCount\n        totalWorkoutPersonalBests\n        totalCollectionReviewCount\n        totalMetadataGroupReviewCount\n      }\n    }\n    fitness {\n      workoutReps\n      workoutCount\n      workoutWeight\n      workoutDistance\n      workoutDuration\n      workoutRestTime\n      measurementCount\n      workoutPersonalBests\n      workoutExercises {\n        count\n        exercise\n      }\n      workoutMuscles {\n        count\n        muscle\n      }\n      workoutEquipments {\n        count\n        equipment\n      }\n    }\n  }\n}": types.UserAnalyticsDocument,
     "query UserDetails {\n  userDetails {\n    __typename\n    ... on User {\n      id\n      lot\n      name\n      isDisabled\n      oidcIssuerId\n      preferences {\n        general {\n          reviewScale\n          gridPacking\n          displayNsfw\n          disableVideos\n          persistQueries\n          disableReviews\n          disableIntegrations\n          disableWatchProviders\n          disableNavigationAnimation\n          dashboard {\n            hidden\n            section\n            numElements\n            deduplicateMedia\n          }\n          watchProviders {\n            lot\n            values\n          }\n        }\n        fitness {\n          logging {\n            muteSounds\n            promptForRestTimer\n            showDetailsWhileEditing\n          }\n          exercises {\n            unitSystem\n            setRestTimers {\n              ...SetRestTimersPart\n            }\n          }\n          measurements {\n            custom {\n              name\n              dataType\n            }\n            inbuilt {\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            }\n          }\n        }\n        notifications {\n          toSend\n          enabled\n        }\n        featuresEnabled {\n          analytics {\n            enabled\n          }\n          others {\n            calendar\n            collections\n          }\n          fitness {\n            enabled\n            workouts\n            templates\n            measurements\n          }\n          media {\n            enabled\n            show\n            book\n            anime\n            manga\n            music\n            movie\n            groups\n            people\n            genres\n            podcast\n            audioBook\n            videoGame\n            visualNovel\n          }\n        }\n      }\n    }\n  }\n}": types.UserDetailsDocument,
     "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      visibility\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}": types.SeenPodcastExtraInformationPartFragmentDoc,
+    "fragment SeenPodcastExtraInformationPart on SeenPodcastExtraInformation {\n  episode\n}\n\nfragment SeenShowExtraInformationPart on SeenShowExtraInformation {\n  episode\n  season\n}\n\nfragment SeenAnimeExtraInformationPart on SeenAnimeExtraInformation {\n  episode\n}\n\nfragment SeenMangaExtraInformationPart on SeenMangaExtraInformation {\n  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,
 };
 
 /**
@@ -51,7 +51,7 @@ export function graphql(source: "query MetadataDetails($metadataId: String!) {\n
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
-export function graphql(source: "query PersonDetails($personId: String!) {\n  personDetails(personId: $personId) {\n    details {\n      id\n      name\n      source\n      identifier\n      isPartial\n      description\n      birthDate\n      deathDate\n      place\n      website\n      gender\n      displayImages\n      sourceUrl\n    }\n    contents {\n      name\n      count\n      items {\n        character\n        metadataId\n      }\n    }\n  }\n}"): (typeof documents)["query PersonDetails($personId: String!) {\n  personDetails(personId: $personId) {\n    details {\n      id\n      name\n      source\n      identifier\n      isPartial\n      description\n      birthDate\n      deathDate\n      place\n      website\n      gender\n      displayImages\n      sourceUrl\n    }\n    contents {\n      name\n      count\n      items {\n        character\n        metadataId\n      }\n    }\n  }\n}"];
+export function graphql(source: "query PersonDetails($personId: String!) {\n  personDetails(personId: $personId) {\n    associatedMetadata {\n      ...PersonDetailsGroupedByRolePart\n    }\n    associatedMetadataGroups {\n      ...PersonDetailsGroupedByRolePart\n    }\n    details {\n      id\n      name\n      place\n      source\n      gender\n      website\n      deathDate\n      birthDate\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}"): (typeof documents)["query PersonDetails($personId: String!) {\n  personDetails(personId: $personId) {\n    associatedMetadata {\n      ...PersonDetailsGroupedByRolePart\n    }\n    associatedMetadataGroups {\n      ...PersonDetailsGroupedByRolePart\n    }\n    details {\n      id\n      name\n      place\n      source\n      gender\n      website\n      deathDate\n      birthDate\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}"];
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
@@ -79,7 +79,7 @@ export function graphql(source: "query GetOidcRedirectUrl {\n  getOidcRedirectUr
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
-export function graphql(source: "fragment SeenPodcastExtraInformationPart on SeenPodcastExtraInformation {\n  episode\n}\n\nfragment SeenShowExtraInformationPart on SeenShowExtraInformation {\n  episode\n  season\n}\n\nfragment SeenAnimeExtraInformationPart on SeenAnimeExtraInformation {\n  episode\n}\n\nfragment SeenMangaExtraInformationPart on SeenMangaExtraInformation {\n  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}"): (typeof documents)["fragment SeenPodcastExtraInformationPart on SeenPodcastExtraInformation {\n  episode\n}\n\nfragment SeenShowExtraInformationPart on SeenShowExtraInformation {\n  episode\n  season\n}\n\nfragment SeenAnimeExtraInformationPart on SeenAnimeExtraInformation {\n  episode\n}\n\nfragment SeenMangaExtraInformationPart on SeenMangaExtraInformation {\n  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}"];
+export function graphql(source: "fragment SeenPodcastExtraInformationPart on SeenPodcastExtraInformation {\n  episode\n}\n\nfragment SeenShowExtraInformationPart on SeenShowExtraInformation {\n  episode\n  season\n}\n\nfragment SeenAnimeExtraInformationPart on SeenAnimeExtraInformation {\n  episode\n}\n\nfragment SeenMangaExtraInformationPart on SeenMangaExtraInformation {\n  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}"): (typeof documents)["fragment SeenPodcastExtraInformationPart on SeenPodcastExtraInformation {\n  episode\n}\n\nfragment SeenShowExtraInformationPart on SeenShowExtraInformation {\n  episode\n  season\n}\n\nfragment SeenAnimeExtraInformationPart on SeenAnimeExtraInformation {\n  episode\n}\n\nfragment SeenMangaExtraInformationPart on SeenMangaExtraInformation {\n  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}"];
 
 export function graphql(source: string) {
   return (documents as any)[source] ?? {};
diff --git a/libs/generated/src/graphql/backend/graphql.ts b/libs/generated/src/graphql/backend/graphql.ts
index ff961f438b..b5b28c9d48 100644
--- a/libs/generated/src/graphql/backend/graphql.ts
+++ b/libs/generated/src/graphql/backend/graphql.ts
@@ -750,7 +750,7 @@ export type GraphqlMetadataDetails = {
   creators: Array<MetadataCreatorGroupedByRole>;
   description?: Maybe<Scalars['String']['output']>;
   genres: Array<GenreListItem>;
-  group?: Maybe<GraphqlMetadataGroup>;
+  group: Array<GraphqlMetadataGroup>;
   id: Scalars['String']['output'];
   identifier: Scalars['String']['output'];
   isNsfw?: Maybe<Scalars['Boolean']['output']>;
@@ -781,6 +781,12 @@ export type GraphqlMetadataGroup = {
   part: Scalars['Int']['output'];
 };
 
+export type GraphqlPersonDetails = {
+  associatedMetadata: Array<PersonDetailsGroupedByRole>;
+  associatedMetadataGroups: Array<PersonDetailsGroupedByRole>;
+  details: Person;
+};
+
 export enum GraphqlSortOrder {
   Asc = 'ASC',
   Desc = 'DESC'
@@ -1040,7 +1046,8 @@ export enum MediaStateChanged {
   MetadataPublished = 'METADATA_PUBLISHED',
   MetadataReleaseDateChanged = 'METADATA_RELEASE_DATE_CHANGED',
   MetadataStatusChanged = 'METADATA_STATUS_CHANGED',
-  PersonMediaAssociated = 'PERSON_MEDIA_ASSOCIATED',
+  PersonMetadataAssociated = 'PERSON_METADATA_ASSOCIATED',
+  PersonMetadataGroupAssociated = 'PERSON_METADATA_GROUP_ASSOCIATED',
   ReviewPosted = 'REVIEW_POSTED'
 }
 
@@ -1623,11 +1630,6 @@ export enum PersonAndMetadataGroupsSortBy {
   Name = 'NAME'
 }
 
-export type PersonDetails = {
-  contents: Array<PersonDetailsGroupedByRole>;
-  details: Person;
-};
-
 export type PersonDetailsGroupedByRole = {
   /** The number of items in this role. */
   count: Scalars['Int']['output'];
@@ -1639,7 +1641,7 @@ export type PersonDetailsGroupedByRole = {
 
 export type PersonDetailsItemWithCharacter = {
   character?: Maybe<Scalars['String']['output']>;
-  metadataId: Scalars['String']['output'];
+  entityId: Scalars['String']['output'];
 };
 
 export type PersonSortInput = {
@@ -1786,7 +1788,7 @@ export type QueryRoot = {
   /** Search for a list of people from a given source. */
   peopleSearch: PeopleSearchResults;
   /** Get details about a creator present in the database. */
-  personDetails: PersonDetails;
+  personDetails: GraphqlPersonDetails;
   /** Get all access links generated by the currently logged in user. */
   userAccessLinks: Array<AccessLink>;
   /** Get the analytics for the currently logged in user. */
@@ -3385,14 +3387,14 @@ export type MetadataDetailsQueryVariables = Exact<{
 }>;
 
 
-export type MetadataDetailsQuery = { metadataDetails: { id: string, lot: MediaLot, title: string, source: MediaSource, isNsfw?: boolean | null, isPartial?: boolean | null, sourceUrl?: string | null, identifier: string, description?: string | null, suggestions: Array<string>, publishYear?: number | null, publishDate?: string | null, providerRating?: string | null, createdByUserId?: string | null, productionStatus?: string | null, originalLanguage?: string | null, genres: Array<{ id: string, name: string }>, group?: { id: string, name: string, part: number } | null, assets: { images: Array<string>, videos: Array<{ videoId: string, source: MetadataVideoSource }> }, creators: Array<{ name: string, items: Array<{ id?: string | null, name: string, image?: string | null, character?: string | null }> }>, watchProviders: Array<{ name: string, image?: string | null, languages: Array<string> }>, animeSpecifics?: { episodes?: number | null } | null, audioBookSpecifics?: { runtime?: number | null } | null, bookSpecifics?: { pages?: number | null } | null, movieSpecifics?: { runtime?: number | null } | null, mangaSpecifics?: { volumes?: number | null, chapters?: string | null } | null, podcastSpecifics?: { totalEpisodes: number, episodes: Array<{ id: string, title: string, overview?: string | null, thumbnail?: string | null, number: number, runtime?: number | null, publishDate: string }> } | null, showSpecifics?: { totalSeasons?: number | null, totalEpisodes?: number | null, runtime?: number | null, seasons: Array<{ id: number, seasonNumber: number, name: string, overview?: string | null, backdropImages: Array<string>, posterImages: Array<string>, episodes: Array<{ id: number, name: string, posterImages: Array<string>, episodeNumber: number, publishDate?: string | null, overview?: string | null, runtime?: number | null }> }> } | null, visualNovelSpecifics?: { length?: number | null } | null, videoGameSpecifics?: { platforms: Array<string> } | null, musicSpecifics?: { duration?: number | null, viewCount?: number | null, byVariousArtists?: boolean | null } | null } };
+export type MetadataDetailsQuery = { metadataDetails: { id: string, lot: MediaLot, title: string, source: MediaSource, isNsfw?: boolean | null, isPartial?: boolean | null, sourceUrl?: string | null, identifier: string, description?: string | null, suggestions: Array<string>, publishYear?: number | null, publishDate?: string | null, providerRating?: string | null, createdByUserId?: string | null, productionStatus?: string | null, originalLanguage?: string | null, genres: Array<{ id: string, name: string }>, group: Array<{ id: string, name: string, part: number }>, assets: { images: Array<string>, videos: Array<{ videoId: string, source: MetadataVideoSource }> }, creators: Array<{ name: string, items: Array<{ id?: string | null, name: string, image?: string | null, character?: string | null }> }>, watchProviders: Array<{ name: string, image?: string | null, languages: Array<string> }>, animeSpecifics?: { episodes?: number | null } | null, audioBookSpecifics?: { runtime?: number | null } | null, bookSpecifics?: { pages?: number | null } | null, movieSpecifics?: { runtime?: number | null } | null, mangaSpecifics?: { volumes?: number | null, chapters?: string | null } | null, podcastSpecifics?: { totalEpisodes: number, episodes: Array<{ id: string, title: string, overview?: string | null, thumbnail?: string | null, number: number, runtime?: number | null, publishDate: string }> } | null, showSpecifics?: { totalSeasons?: number | null, totalEpisodes?: number | null, runtime?: number | null, seasons: Array<{ id: number, seasonNumber: number, name: string, overview?: string | null, backdropImages: Array<string>, posterImages: Array<string>, episodes: Array<{ id: number, name: string, posterImages: Array<string>, episodeNumber: number, publishDate?: string | null, overview?: string | null, runtime?: number | null }> }> } | null, visualNovelSpecifics?: { length?: number | null } | null, videoGameSpecifics?: { platforms: Array<string> } | null, musicSpecifics?: { duration?: number | null, viewCount?: number | null, byVariousArtists?: boolean | null } | null } };
 
 export type PersonDetailsQueryVariables = Exact<{
   personId: Scalars['String']['input'];
 }>;
 
 
-export type PersonDetailsQuery = { personDetails: { details: { id: string, name: string, source: MediaSource, identifier: string, isPartial?: boolean | null, description?: string | null, birthDate?: string | null, deathDate?: string | null, place?: string | null, website?: string | null, gender?: string | null, displayImages: Array<string>, sourceUrl?: string | null }, contents: Array<{ name: string, count: number, items: Array<{ character?: string | null, metadataId: string }> }> } };
+export type PersonDetailsQuery = { personDetails: { associatedMetadata: Array<{ name: string, count: number, items: Array<{ entityId: string, character?: string | null }> }>, associatedMetadataGroups: Array<{ name: string, count: number, items: Array<{ entityId: string, character?: string | null }> }>, details: { id: string, name: string, place?: string | null, source: MediaSource, gender?: string | null, website?: string | null, deathDate?: string | null, birthDate?: string | null, isPartial?: boolean | null, sourceUrl?: string | null, identifier: string, description?: string | null, displayImages: Array<string> } } };
 
 export type UserAnalyticsQueryVariables = Exact<{
   input: UserAnalyticsInput;
@@ -3686,6 +3688,8 @@ export type WorkoutInformationPartFragment = { comment?: string | null, assets?:
 
 export type SetRestTimersPartFragment = { drop?: number | null, warmup?: number | null, normal?: number | null, failure?: number | null };
 
+export type PersonDetailsGroupedByRolePartFragment = { name: string, count: number, items: Array<{ entityId: string, character?: string | null }> };
+
 export const SeenShowExtraInformationPartFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}}]} as unknown as DocumentNode<SeenShowExtraInformationPartFragment, unknown>;
 export const SeenPodcastExtraInformationPartFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}}]} as unknown as DocumentNode<SeenPodcastExtraInformationPartFragment, unknown>;
 export const SeenAnimeExtraInformationPartFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenAnimeExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}}]} as unknown as DocumentNode<SeenAnimeExtraInformationPartFragment, unknown>;
@@ -3702,6 +3706,7 @@ export const ReviewItemPartFragmentDoc = {"kind":"Document","definitions":[{"kin
 export const EntityAssetsPartFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EntityAssetsPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EntityAssets"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"images"}},{"kind":"Field","name":{"kind":"Name","value":"videos"}}]}}]} as unknown as DocumentNode<EntityAssetsPartFragment, unknown>;
 export const WorkoutInformationPartFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkoutInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"comment"}},{"kind":"Field","name":{"kind":"Name","value":"assets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"EntityAssetsPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"supersets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"exercises"}}]}},{"kind":"Field","name":{"kind":"Name","value":"exercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"notes"}},{"kind":"Field","name":{"kind":"Name","value":"total"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutOrExerciseTotalsPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"assets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"EntityAssetsPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"sets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"rpe"}},{"kind":"Field","name":{"kind":"Name","value":"note"}},{"kind":"Field","name":{"kind":"Name","value":"restTime"}},{"kind":"Field","name":{"kind":"Name","value":"confirmedAt"}},{"kind":"Field","name":{"kind":"Name","value":"personalBests"}},{"kind":"Field","name":{"kind":"Name","value":"statistic"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutSetStatisticPart"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"EntityAssetsPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"EntityAssets"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"images"}},{"kind":"Field","name":{"kind":"Name","value":"videos"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkoutOrExerciseTotalsPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutOrExerciseTotals"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"reps"}},{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"distance"}},{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"restTime"}},{"kind":"Field","name":{"kind":"Name","value":"personalBestsAchieved"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkoutSetStatisticPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutSetStatistic"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"reps"}},{"kind":"Field","name":{"kind":"Name","value":"pace"}},{"kind":"Field","name":{"kind":"Name","value":"oneRm"}},{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"distance"}}]}}]} as unknown as DocumentNode<WorkoutInformationPartFragment, unknown>;
 export const SetRestTimersPartFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SetRestTimersPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SetRestTimersSettings"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"drop"}},{"kind":"Field","name":{"kind":"Name","value":"warmup"}},{"kind":"Field","name":{"kind":"Name","value":"normal"}},{"kind":"Field","name":{"kind":"Name","value":"failure"}}]}}]} as unknown as DocumentNode<SetRestTimersPartFragment, unknown>;
+export const PersonDetailsGroupedByRolePartFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PersonDetailsGroupedByRolePart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonDetailsGroupedByRole"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entityId"}},{"kind":"Field","name":{"kind":"Name","value":"character"}}]}}]}}]} as unknown as DocumentNode<PersonDetailsGroupedByRolePartFragment, unknown>;
 export const RegisterUserDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RegisterUser"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RegisterUserInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"registerUser"},"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":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RegisterError"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StringIdObject"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode<RegisterUserMutation, RegisterUserMutationVariables>;
 export const LoginUserDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"LoginUser"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AuthUserInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"loginUser"},"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":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LoginError"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LoginResponse"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"apiKey"}}]}}]}}]}}]} as unknown as DocumentNode<LoginUserMutation, LoginUserMutationVariables>;
 export const AddEntityToCollectionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddEntityToCollection"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ChangeCollectionToEntityInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addEntityToCollection"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode<AddEntityToCollectionMutation, AddEntityToCollectionMutationVariables>;
@@ -3756,7 +3761,7 @@ export const UpdateUserExerciseSettingsDocument = {"kind":"Document","definition
 export const MergeExerciseDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"MergeExercise"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"mergeFrom"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"mergeInto"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mergeExercise"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"mergeFrom"},"value":{"kind":"Variable","name":{"kind":"Name","value":"mergeFrom"}}},{"kind":"Argument","name":{"kind":"Name","value":"mergeInto"},"value":{"kind":"Variable","name":{"kind":"Name","value":"mergeInto"}}}]}]}}]} as unknown as DocumentNode<MergeExerciseMutation, MergeExerciseMutationVariables>;
 export const RefreshUserRecommendationsKeyDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RefreshUserRecommendationsKey"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"refreshUserRecommendationsKey"}}]}}]} as unknown as DocumentNode<RefreshUserRecommendationsKeyMutation, RefreshUserRecommendationsKeyMutationVariables>;
 export const MetadataDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"metadataId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"metadataId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"metadataId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"isNsfw"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"suggestions"}},{"kind":"Field","name":{"kind":"Name","value":"publishYear"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}},{"kind":"Field","name":{"kind":"Name","value":"providerRating"}},{"kind":"Field","name":{"kind":"Name","value":"createdByUserId"}},{"kind":"Field","name":{"kind":"Name","value":"productionStatus"}},{"kind":"Field","name":{"kind":"Name","value":"originalLanguage"}},{"kind":"Field","name":{"kind":"Name","value":"genres"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"part"}}]}},{"kind":"Field","name":{"kind":"Name","value":"assets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"images"}},{"kind":"Field","name":{"kind":"Name","value":"videos"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"videoId"}},{"kind":"Field","name":{"kind":"Name","value":"source"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"creators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"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":"image"}},{"kind":"Field","name":{"kind":"Name","value":"character"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"watchProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"languages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodes"}}]}},{"kind":"Field","name":{"kind":"Name","value":"audioBookSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}},{"kind":"Field","name":{"kind":"Name","value":"bookSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"movieSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"volumes"}},{"kind":"Field","name":{"kind":"Name","value":"chapters"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"thumbnail"}},{"kind":"Field","name":{"kind":"Name","value":"number"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalEpisodes"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalSeasons"}},{"kind":"Field","name":{"kind":"Name","value":"totalEpisodes"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"seasons"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"seasonNumber"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"backdropImages"}},{"kind":"Field","name":{"kind":"Name","value":"posterImages"}},{"kind":"Field","name":{"kind":"Name","value":"episodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"posterImages"}},{"kind":"Field","name":{"kind":"Name","value":"episodeNumber"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"visualNovelSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"length"}}]}},{"kind":"Field","name":{"kind":"Name","value":"videoGameSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"platforms"}}]}},{"kind":"Field","name":{"kind":"Name","value":"musicSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"viewCount"}},{"kind":"Field","name":{"kind":"Name","value":"byVariousArtists"}}]}}]}}]}}]} as unknown as DocumentNode<MetadataDetailsQuery, MetadataDetailsQueryVariables>;
-export const PersonDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PersonDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"personId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"personId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"personId"}}}],"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":"source"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"birthDate"}},{"kind":"Field","name":{"kind":"Name","value":"deathDate"}},{"kind":"Field","name":{"kind":"Name","value":"place"}},{"kind":"Field","name":{"kind":"Name","value":"website"}},{"kind":"Field","name":{"kind":"Name","value":"gender"}},{"kind":"Field","name":{"kind":"Name","value":"displayImages"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"contents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"character"}},{"kind":"Field","name":{"kind":"Name","value":"metadataId"}}]}}]}}]}}]}}]} as unknown as DocumentNode<PersonDetailsQuery, PersonDetailsQueryVariables>;
+export const PersonDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PersonDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"personId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"personId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"personId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"associatedMetadata"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PersonDetailsGroupedByRolePart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"associatedMetadataGroups"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PersonDetailsGroupedByRolePart"}}]}},{"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":"place"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"gender"}},{"kind":"Field","name":{"kind":"Name","value":"website"}},{"kind":"Field","name":{"kind":"Name","value":"deathDate"}},{"kind":"Field","name":{"kind":"Name","value":"birthDate"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"displayImages"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PersonDetailsGroupedByRolePart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PersonDetailsGroupedByRole"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entityId"}},{"kind":"Field","name":{"kind":"Name","value":"character"}}]}}]}}]} as unknown as DocumentNode<PersonDetailsQuery, PersonDetailsQueryVariables>;
 export const UserAnalyticsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserAnalytics"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UserAnalyticsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userAnalytics"},"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":"hours"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hour"}},{"kind":"Field","name":{"kind":"Name","value":"entities"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entityId"}},{"kind":"Field","name":{"kind":"Name","value":"entityLot"}},{"kind":"Field","name":{"kind":"Name","value":"metadataLot"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"activities"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"groupedBy"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalDuration"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"day"}},{"kind":"Field","name":{"kind":"Name","value":"bookCount"}},{"kind":"Field","name":{"kind":"Name","value":"showCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"movieCount"}},{"kind":"Field","name":{"kind":"Name","value":"animeCount"}},{"kind":"Field","name":{"kind":"Name","value":"mangaCount"}},{"kind":"Field","name":{"kind":"Name","value":"workoutCount"}},{"kind":"Field","name":{"kind":"Name","value":"podcastCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalBookPages"}},{"kind":"Field","name":{"kind":"Name","value":"audioBookCount"}},{"kind":"Field","name":{"kind":"Name","value":"videoGameCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalReviewCount"}},{"kind":"Field","name":{"kind":"Name","value":"visualNovelCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutReps"}},{"kind":"Field","name":{"kind":"Name","value":"totalShowDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalMovieDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalMetadataCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutWeight"}},{"kind":"Field","name":{"kind":"Name","value":"totalPodcastDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutDistance"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutRestTime"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutDuration"}},{"kind":"Field","name":{"kind":"Name","value":"userMeasurementCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalVideoGameDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalAudioBookDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalPersonReviewCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalVisualNovelDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalMetadataReviewCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutPersonalBests"}},{"kind":"Field","name":{"kind":"Name","value":"totalCollectionReviewCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalMetadataGroupReviewCount"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workoutReps"}},{"kind":"Field","name":{"kind":"Name","value":"workoutCount"}},{"kind":"Field","name":{"kind":"Name","value":"workoutWeight"}},{"kind":"Field","name":{"kind":"Name","value":"workoutDistance"}},{"kind":"Field","name":{"kind":"Name","value":"workoutDuration"}},{"kind":"Field","name":{"kind":"Name","value":"workoutRestTime"}},{"kind":"Field","name":{"kind":"Name","value":"measurementCount"}},{"kind":"Field","name":{"kind":"Name","value":"workoutPersonalBests"}},{"kind":"Field","name":{"kind":"Name","value":"workoutExercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"exercise"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workoutMuscles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workoutEquipments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"equipment"}}]}}]}}]}}]}}]} as unknown as DocumentNode<UserAnalyticsQuery, UserAnalyticsQueryVariables>;
 export const UserDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"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":"isDisabled"}},{"kind":"Field","name":{"kind":"Name","value":"oidcIssuerId"}},{"kind":"Field","name":{"kind":"Name","value":"preferences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"reviewScale"}},{"kind":"Field","name":{"kind":"Name","value":"gridPacking"}},{"kind":"Field","name":{"kind":"Name","value":"displayNsfw"}},{"kind":"Field","name":{"kind":"Name","value":"disableVideos"}},{"kind":"Field","name":{"kind":"Name","value":"persistQueries"}},{"kind":"Field","name":{"kind":"Name","value":"disableReviews"}},{"kind":"Field","name":{"kind":"Name","value":"disableIntegrations"}},{"kind":"Field","name":{"kind":"Name","value":"disableWatchProviders"}},{"kind":"Field","name":{"kind":"Name","value":"disableNavigationAnimation"}},{"kind":"Field","name":{"kind":"Name","value":"dashboard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hidden"}},{"kind":"Field","name":{"kind":"Name","value":"section"}},{"kind":"Field","name":{"kind":"Name","value":"numElements"}},{"kind":"Field","name":{"kind":"Name","value":"deduplicateMedia"}}]}},{"kind":"Field","name":{"kind":"Name","value":"watchProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"values"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"logging"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"muteSounds"}},{"kind":"Field","name":{"kind":"Name","value":"promptForRestTimer"}},{"kind":"Field","name":{"kind":"Name","value":"showDetailsWhileEditing"}}]}},{"kind":"Field","name":{"kind":"Name","value":"exercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unitSystem"}},{"kind":"Field","name":{"kind":"Name","value":"setRestTimers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SetRestTimersPart"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"measurements"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"custom"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"dataType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"inbuilt"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"bodyMassIndex"}},{"kind":"Field","name":{"kind":"Name","value":"totalBodyWater"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}},{"kind":"Field","name":{"kind":"Name","value":"leanBodyMass"}},{"kind":"Field","name":{"kind":"Name","value":"bodyFat"}},{"kind":"Field","name":{"kind":"Name","value":"boneMass"}},{"kind":"Field","name":{"kind":"Name","value":"visceralFat"}},{"kind":"Field","name":{"kind":"Name","value":"waistCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"waistToHeightRatio"}},{"kind":"Field","name":{"kind":"Name","value":"hipCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"waistToHipRatio"}},{"kind":"Field","name":{"kind":"Name","value":"chestCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"thighCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"bicepsCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"neckCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"bodyFatCaliper"}},{"kind":"Field","name":{"kind":"Name","value":"chestSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"abdominalSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"thighSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"basalMetabolicRate"}},{"kind":"Field","name":{"kind":"Name","value":"totalDailyEnergyExpenditure"}},{"kind":"Field","name":{"kind":"Name","value":"calories"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"notifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"toSend"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}}]}},{"kind":"Field","name":{"kind":"Name","value":"featuresEnabled"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"analytics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}}]}},{"kind":"Field","name":{"kind":"Name","value":"others"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"calendar"}},{"kind":"Field","name":{"kind":"Name","value":"collections"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"workouts"}},{"kind":"Field","name":{"kind":"Name","value":"templates"}},{"kind":"Field","name":{"kind":"Name","value":"measurements"}}]}},{"kind":"Field","name":{"kind":"Name","value":"media"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"show"}},{"kind":"Field","name":{"kind":"Name","value":"book"}},{"kind":"Field","name":{"kind":"Name","value":"anime"}},{"kind":"Field","name":{"kind":"Name","value":"manga"}},{"kind":"Field","name":{"kind":"Name","value":"music"}},{"kind":"Field","name":{"kind":"Name","value":"movie"}},{"kind":"Field","name":{"kind":"Name","value":"groups"}},{"kind":"Field","name":{"kind":"Name","value":"people"}},{"kind":"Field","name":{"kind":"Name","value":"genres"}},{"kind":"Field","name":{"kind":"Name","value":"podcast"}},{"kind":"Field","name":{"kind":"Name","value":"audioBook"}},{"kind":"Field","name":{"kind":"Name","value":"videoGame"}},{"kind":"Field","name":{"kind":"Name","value":"visualNovel"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SetRestTimersPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SetRestTimersSettings"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"drop"}},{"kind":"Field","name":{"kind":"Name","value":"warmup"}},{"kind":"Field","name":{"kind":"Name","value":"normal"}},{"kind":"Field","name":{"kind":"Name","value":"failure"}}]}}]} as unknown as DocumentNode<UserDetailsQuery, UserDetailsQueryVariables>;
 export const UserExerciseDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserExerciseDetails"},"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":"userExerciseDetails"},"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":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"history"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"idx"}},{"kind":"Field","name":{"kind":"Name","value":"workoutId"}},{"kind":"Field","name":{"kind":"Name","value":"workoutEndOn"}},{"kind":"Field","name":{"kind":"Name","value":"bestSet"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutSetRecordPart"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"exerciseId"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedOn"}},{"kind":"Field","name":{"kind":"Name","value":"exerciseNumTimesInteracted"}},{"kind":"Field","name":{"kind":"Name","value":"exerciseExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"settings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"excludeFromAnalytics"}},{"kind":"Field","name":{"kind":"Name","value":"setRestTimers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SetRestTimersPart"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"lifetimeStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"reps"}},{"kind":"Field","name":{"kind":"Name","value":"distance"}},{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"personalBestsAchieved"}}]}},{"kind":"Field","name":{"kind":"Name","value":"personalBests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"sets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setIdx"}},{"kind":"Field","name":{"kind":"Name","value":"workoutId"}},{"kind":"Field","name":{"kind":"Name","value":"exerciseIdx"}}]}}]}}]}}]}}]}}]}},{"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":"WorkoutSetStatisticPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutSetStatistic"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"reps"}},{"kind":"Field","name":{"kind":"Name","value":"pace"}},{"kind":"Field","name":{"kind":"Name","value":"oneRm"}},{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"distance"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Collection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}}]}},{"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"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkoutSetRecordPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutSetRecord"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"personalBests"}},{"kind":"Field","name":{"kind":"Name","value":"statistic"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutSetStatisticPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SetRestTimersPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SetRestTimersSettings"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"drop"}},{"kind":"Field","name":{"kind":"Name","value":"warmup"}},{"kind":"Field","name":{"kind":"Name","value":"normal"}},{"kind":"Field","name":{"kind":"Name","value":"failure"}}]}}]} as unknown as DocumentNode<UserExerciseDetailsQuery, UserExerciseDetailsQueryVariables>;
diff --git a/libs/generated/src/graphql/backend/types.generated.ts b/libs/generated/src/graphql/backend/types.generated.ts
index c9f9ba98fb..1f4ff5eeb0 100644
--- a/libs/generated/src/graphql/backend/types.generated.ts
+++ b/libs/generated/src/graphql/backend/types.generated.ts
@@ -762,7 +762,7 @@ export type GraphqlMetadataDetails = {
   creators: Array<MetadataCreatorGroupedByRole>;
   description?: Maybe<Scalars['String']['output']>;
   genres: Array<GenreListItem>;
-  group?: Maybe<GraphqlMetadataGroup>;
+  group: Array<GraphqlMetadataGroup>;
   id: Scalars['String']['output'];
   identifier: Scalars['String']['output'];
   isNsfw?: Maybe<Scalars['Boolean']['output']>;
@@ -794,6 +794,13 @@ export type GraphqlMetadataGroup = {
   part: Scalars['Int']['output'];
 };
 
+export type GraphqlPersonDetails = {
+  __typename?: 'GraphqlPersonDetails';
+  associatedMetadata: Array<PersonDetailsGroupedByRole>;
+  associatedMetadataGroups: Array<PersonDetailsGroupedByRole>;
+  details: Person;
+};
+
 export enum GraphqlSortOrder {
   Asc = 'ASC',
   Desc = 'DESC'
@@ -1067,7 +1074,8 @@ export enum MediaStateChanged {
   MetadataPublished = 'METADATA_PUBLISHED',
   MetadataReleaseDateChanged = 'METADATA_RELEASE_DATE_CHANGED',
   MetadataStatusChanged = 'METADATA_STATUS_CHANGED',
-  PersonMediaAssociated = 'PERSON_MEDIA_ASSOCIATED',
+  PersonMetadataAssociated = 'PERSON_METADATA_ASSOCIATED',
+  PersonMetadataGroupAssociated = 'PERSON_METADATA_GROUP_ASSOCIATED',
   ReviewPosted = 'REVIEW_POSTED'
 }
 
@@ -1668,12 +1676,6 @@ export enum PersonAndMetadataGroupsSortBy {
   Name = 'NAME'
 }
 
-export type PersonDetails = {
-  __typename?: 'PersonDetails';
-  contents: Array<PersonDetailsGroupedByRole>;
-  details: Person;
-};
-
 export type PersonDetailsGroupedByRole = {
   __typename?: 'PersonDetailsGroupedByRole';
   /** The number of items in this role. */
@@ -1687,7 +1689,7 @@ export type PersonDetailsGroupedByRole = {
 export type PersonDetailsItemWithCharacter = {
   __typename?: 'PersonDetailsItemWithCharacter';
   character?: Maybe<Scalars['String']['output']>;
-  metadataId: Scalars['String']['output'];
+  entityId: Scalars['String']['output'];
 };
 
 export type PersonSortInput = {
@@ -1842,7 +1844,7 @@ export type QueryRoot = {
   /** Search for a list of people from a given source. */
   peopleSearch: PeopleSearchResults;
   /** Get details about a creator present in the database. */
-  personDetails: PersonDetails;
+  personDetails: GraphqlPersonDetails;
   /** Get all access links generated by the currently logged in user. */
   userAccessLinks: Array<AccessLink>;
   /** Get the analytics for the currently logged in user. */
diff --git a/libs/graphql/src/backend/queries/PersonDetails.gql b/libs/graphql/src/backend/queries/PersonDetails.gql
index 16a397aaeb..2b766771be 100644
--- a/libs/graphql/src/backend/queries/PersonDetails.gql
+++ b/libs/graphql/src/backend/queries/PersonDetails.gql
@@ -1,27 +1,25 @@
 query PersonDetails($personId: String!) {
 	personDetails(personId: $personId) {
+		associatedMetadata {
+			...PersonDetailsGroupedByRolePart
+		}
+		associatedMetadataGroups {
+			...PersonDetailsGroupedByRolePart
+		}
 		details {
 			id
 			name
+			place
 			source
-			identifier
+			gender
+			website
+			deathDate
+			birthDate
 			isPartial
+			sourceUrl
+			identifier
 			description
-			birthDate
-			deathDate
-			place
-			website
-			gender
 			displayImages
-			sourceUrl
-		}
-		contents {
-			name
-			count
-			items {
-				character
-				metadataId
-			}
 		}
 	}
 }
diff --git a/libs/graphql/src/backend/queries/fragments.gql b/libs/graphql/src/backend/queries/fragments.gql
index 38981df69f..edf8dd92dd 100644
--- a/libs/graphql/src/backend/queries/fragments.gql
+++ b/libs/graphql/src/backend/queries/fragments.gql
@@ -218,3 +218,12 @@ fragment SetRestTimersPart on SetRestTimersSettings {
 	normal
 	failure
 }
+
+fragment PersonDetailsGroupedByRolePart on PersonDetailsGroupedByRole {
+	name
+	count
+	items {
+		entityId
+		character
+	}
+}


Test patch: