diff --git a/Cargo.lock b/Cargo.lock index 8303b6847a..ae1130e72b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4507,7 +4507,7 @@ checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f" [[package]] name = "ryot" -version = "2.12.1" +version = "2.12.2" dependencies = [ "anyhow", "apalis", diff --git a/apps/backend/Cargo.toml b/apps/backend/Cargo.toml index b577ba1091..46d297f9eb 100644 --- a/apps/backend/Cargo.toml +++ b/apps/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ryot" -version = "2.12.1" +version = "2.12.2" edition = "2021" repository = "https://github.com/IgnisDa/ryot" license = "GPL-V3" diff --git a/apps/backend/src/importer/movary.rs b/apps/backend/src/importer/movary.rs index 6f70bf8947..915d3ae546 100644 --- a/apps/backend/src/importer/movary.rs +++ b/apps/backend/src/importer/movary.rs @@ -72,10 +72,7 @@ pub async fn import(input: DeployMovaryImportInput) -> Result { reviews: vec![ImportOrExportItemRating { // DEV: Rates items out of 10 rating: Some(record.user_rating.saturating_mul(dec!(10))), - review: None, - show_season_number: None, - show_episode_number: None, - podcast_episode_number: None, + ..Default::default() }], collections: vec![], }) @@ -149,10 +146,7 @@ pub async fn import(input: DeployMovaryImportInput) -> Result { if review.is_some() { reviews.push(ImportOrExportItemRating { review, - rating: None, - show_season_number: None, - show_episode_number: None, - podcast_episode_number: None, + ..Default::default() }) } media.push(ImportOrExportMediaItem { diff --git a/apps/backend/src/miscellaneous/resolver.rs b/apps/backend/src/miscellaneous/resolver.rs index 30af374e0b..1dab744f16 100644 --- a/apps/backend/src/miscellaneous/resolver.rs +++ b/apps/backend/src/miscellaneous/resolver.rs @@ -68,15 +68,15 @@ use crate::{ media::{ AddMediaToCollection, AnimeSpecifics, AudioBookSpecifics, BookSpecifics, CreateOrUpdateCollectionInput, CreatorExtraInformation, ImportOrExportItemRating, - ImportOrExportItemReview, ImportOrExportMediaItem, ImportOrExportMediaItemSeen, - ImportOrExportPersonItem, MangaSpecifics, MediaCreatorSearchItem, MediaDetails, - MediaListItem, MediaSearchItem, MediaSearchItemResponse, MediaSearchItemWithLot, - MediaSpecifics, MetadataCreator, MetadataImage, MetadataImages, MetadataSuggestion, - MovieSpecifics, PodcastSpecifics, PostReviewInput, ProgressUpdateError, - ProgressUpdateErrorVariant, ProgressUpdateInput, ProgressUpdateResultUnion, - ReviewComment, ReviewCommentUser, ReviewComments, SeenOrReviewExtraInformation, - SeenPodcastExtraInformation, SeenShowExtraInformation, ShowSpecifics, - UserMediaReminder, UserSummary, VideoGameSpecifics, Visibility, + ImportOrExportItemReview, ImportOrExportItemReviewComment, ImportOrExportMediaItem, + ImportOrExportMediaItemSeen, ImportOrExportPersonItem, MangaSpecifics, + MediaCreatorSearchItem, MediaDetails, MediaListItem, MediaSearchItem, + MediaSearchItemResponse, MediaSearchItemWithLot, MediaSpecifics, MetadataCreator, + MetadataImage, MetadataImages, MetadataSuggestion, MovieSpecifics, PodcastSpecifics, + PostReviewInput, ProgressUpdateError, ProgressUpdateErrorVariant, ProgressUpdateInput, + ProgressUpdateResultUnion, ReviewCommentUser, ReviewComments, + SeenOrReviewExtraInformation, SeenPodcastExtraInformation, SeenShowExtraInformation, + ShowSpecifics, UserMediaReminder, UserSummary, VideoGameSpecifics, Visibility, }, IdObject, SearchDetails, SearchInput, SearchResults, StoredUrl, }, @@ -319,7 +319,7 @@ struct ReviewItem { show_season: Option, show_episode: Option, podcast_episode: Option, - comments: Vec, + comments: Vec, } #[derive(Debug, SimpleObject)] @@ -5013,7 +5013,7 @@ impl MiscellaneousService { comment.liked_by.remove(&user_id); } else { let user = user_by_id(&self.db, user_id).await?; - comments.push(ReviewComment { + comments.push(ImportOrExportItemReviewComment { id: nanoid!(20), text: input.text.unwrap(), user: ReviewCommentUser { @@ -5057,5 +5057,9 @@ fn get_review_export_item(rev: ReviewItem) -> ImportOrExportItemRating { show_season_number: rev.show_season, show_episode_number: rev.show_episode, podcast_episode_number: rev.podcast_episode, + comments: match rev.comments.is_empty() { + true => None, + false => Some(rev.comments), + }, } } diff --git a/apps/backend/src/models.rs b/apps/backend/src/models.rs index f6eb468b28..acde970082 100644 --- a/apps/backend/src/models.rs +++ b/apps/backend/src/models.rs @@ -651,6 +651,8 @@ pub mod media { pub show_episode_number: Option, /// If for a podcast, the episode for which this review was for. pub podcast_episode_number: Option, + /// The comments attached to this review. + pub comments: Option>, } /// Details about a specific media item that needs to be imported or exported. @@ -730,6 +732,7 @@ pub mod media { Default, Hash, SimpleObject, + Type, )] pub struct ReviewCommentUser { pub id: i32, @@ -746,8 +749,9 @@ pub mod media { Deserialize, Default, SimpleObject, + Type, )] - pub struct ReviewComment { + pub struct ImportOrExportItemReviewComment { pub id: String, pub text: String, pub user: ReviewCommentUser, @@ -758,7 +762,7 @@ pub mod media { // FIXME: Remove this #[derive(Clone, Debug, PartialEq, FromJsonQueryResult, Eq, Serialize, Deserialize, Default)] - pub struct ReviewComments(pub Vec); + pub struct ReviewComments(pub Vec); #[derive( Clone, diff --git a/apps/backend/src/providers/google_books.rs b/apps/backend/src/providers/google_books.rs index c861111d89..e3341060fc 100644 --- a/apps/backend/src/providers/google_books.rs +++ b/apps/backend/src/providers/google_books.rs @@ -156,6 +156,7 @@ impl MediaProvider for GoogleBooksService { }) } } + impl GoogleBooksService { fn google_books_response_to_search_response( &self, diff --git a/apps/backend/src/routes.rs b/apps/backend/src/routes.rs index 40d4f8679a..427c8c695e 100644 --- a/apps/backend/src/routes.rs +++ b/apps/backend/src/routes.rs @@ -112,7 +112,12 @@ pub async fn json_export( Extension(exercise_service): Extension>, ctx: AuthContext, ) -> Result, (StatusCode, Json)> { - let user_id = ctx.user_id.unwrap(); + let user_id = ctx.user_id.ok_or_else(|| { + ( + StatusCode::FORBIDDEN, + Json(json!({"err": "User is not authenticated"})), + ) + })?; let resp = match export_type.as_str() { "all" => { let media = media_service.export_media(user_id).await.unwrap(); diff --git a/apps/frontend/src/lib/components/MediaComponents.tsx b/apps/frontend/src/lib/components/MediaComponents.tsx index 51a3607eca..bfc54aeec5 100644 --- a/apps/frontend/src/lib/components/MediaComponents.tsx +++ b/apps/frontend/src/lib/components/MediaComponents.tsx @@ -14,6 +14,7 @@ import { Box, Button, Collapse, + Divider, Flex, Image, Loader, @@ -90,160 +91,165 @@ export const ReviewItemDisplay = ({ }); return userPreferences.data ? ( - - - - {getInitials(review.postedBy?.name || "")}{" "} - - - {review.postedBy?.name} - - {DateTime.fromJSDate( - review.postedOn || new Date(), - ).toLocaleString()} - - - {user && user.id === review.postedBy?.id ? ( - - - - - - - - ) : undefined} - - - {typeof review.showSeason === "number" ? ( - - S{review.showSeason}-E - {review.showEpisode} - - ) : undefined} - {typeof review.podcastEpisode === "number" ? ( - EP-{review.podcastEpisode} - ) : undefined} - {review.rating > 0 ? ( - - - ({ - color: theme.colorScheme === "dark" ? "white" : undefined, + <> + + + + {getInitials(review.postedBy?.name || "")}{" "} + + + {review.postedBy?.name} + + {DateTime.fromJSDate( + review.postedOn || new Date(), + ).toLocaleString()} + + + {user && user.id === review.postedBy?.id ? ( + - {review.rating} - {userPreferences.data.general.reviewScale === - UserReviewScale.OutOfFive - ? undefined - : "%"} + + + + + + + ) : undefined} + + + {typeof review.showSeason === "number" ? ( + + S{review.showSeason}-E + {review.showEpisode} - - ) : undefined} - {review.text ? ( - !review.spoiler ? ( - -
- - ) : ( - <> - {!opened ? ( - - ) : undefined} - - - - - ) - ) : undefined} - - {review.comments?.length || 0 > 0 ? ( - - - {review.comments - ? review.comments.map((c) => ( - - - - {getInitials(c?.user?.name || "")}{" "} - - - {c?.user?.name} - - {DateTime.fromJSDate( - c?.createdOn || new Date(), - ).toLocaleString()} - - - {user && user.id === c?.user?.id ? ( + ) : undefined} + {typeof review.podcastEpisode === "number" ? ( + EP-{review.podcastEpisode} + ) : undefined} + {review.rating > 0 ? ( + + + ({ + color: theme.colorScheme === "dark" ? "white" : undefined, + })} + fw="bold" + > + {review.rating} + {userPreferences.data.general.reviewScale === + UserReviewScale.OutOfFive + ? undefined + : "%"} + + + ) : undefined} + {review.text ? ( + !review.spoiler ? ( + +
+ + ) : ( + <> + {!opened ? ( + + ) : undefined} + + + + + ) + ) : undefined} + + {review.comments?.length || 0 > 0 ? ( + + + {review.comments + ? review.comments.map((c) => ( + + + + {getInitials(c?.user?.name || "")}{" "} + + + {c?.user?.name} + + {DateTime.fromJSDate( + c?.createdOn || new Date(), + ).toLocaleString()} + + + {user && user.id === c?.user?.id ? ( + { + const yes = confirm( + "Are you sure you want to delete this comment?", + ); + if (review.id && yes) + createReviewComment.mutate({ + input: { + reviewId: review.id, + commentId: c.id, + shouldDelete: true, + }, + }); + }} + > + + + ) : undefined} { - const yes = confirm( - "Are you sure you want to delete this comment?", + const likedByUser = c?.likedBy?.includes( + user?.id, ); - if (review.id && yes) + if (review.id) createReviewComment.mutate({ input: { reviewId: review.id, - commentId: c.id, - shouldDelete: true, + commentId: c?.id, + incrementLikes: !likedByUser, + decrementLikes: likedByUser, }, }); }} > - + + {c?.likedBy?.length} - ) : undefined} - { - const likedByUser = c?.likedBy?.includes(user?.id); - if (review.id) - createReviewComment.mutate({ - input: { - reviewId: review.id, - commentId: c?.id, - incrementLikes: !likedByUser, - decrementLikes: likedByUser, - }, - }); - }} - > - - {c?.likedBy?.length} - - - {c?.text} - - )) - : undefined} - - - ) : undefined} + + {c?.text} + + )) + : undefined} + + + ) : undefined} + - + + ) : undefined; }; diff --git a/apps/frontend/src/pages/media/people/index.tsx b/apps/frontend/src/pages/media/people/index.tsx index f097c333d6..86c261f731 100644 --- a/apps/frontend/src/pages/media/people/index.tsx +++ b/apps/frontend/src/pages/media/people/index.tsx @@ -57,7 +57,6 @@ const Page: NextPageWithLayout = () => { ); return userCreatorDetails; }, - staleTime: Infinity, enabled: !!creatorId, }); const creatorDetails = useQuery({ @@ -65,9 +64,7 @@ const Page: NextPageWithLayout = () => { queryFn: async () => { const { creatorDetails } = await gqlClient.request( CreatorDetailsDocument, - { - creatorId, - }, + { creatorId }, ); return creatorDetails; }, diff --git a/docs/includes/export-schema.ts b/docs/includes/export-schema.ts index 34f2a9d240..a8809cef0f 100644 --- a/docs/includes/export-schema.ts +++ b/docs/includes/export-schema.ts @@ -13,21 +13,13 @@ export type MetadataSource = | "Openlibrary" | "Tmdb"; -/** - * Complete export of the user. - */ -export type ExportAllResponse = { - media: ImportOrExportMediaItem[]; - people: ImportOrExportPersonItem[]; - measurements: ExportUserMeasurementItem[]; -}; - export type ImportOrExportItemRating = { review: ImportOrExportItemReview | null; rating: string | null; show_season_number: number | null; show_episode_number: number | null; podcast_episode_number: number | null; + comments: ImportOrExportItemReviewComment[] | null; }; export type ImportOrExportMediaItemSeen = { @@ -40,31 +32,14 @@ export type ImportOrExportMediaItemSeen = { }; /** - * Details about a specific creator item that needs to be exported. + * Complete export of the user. */ -export type ImportOrExportPersonItem = { - name: string; - reviews: ImportOrExportItemRating[]; -}; - -export type ExportUserMeasurementItem = { - timestamp: string; - user_id: number; - name: string | null; - comment: string | null; - stats: UserMeasurementStats; +export type ExportAllResponse = { + media: ImportOrExportMediaItem[]; + people: ImportOrExportPersonItem[]; + measurements: ExportUserMeasurementItem[]; }; -export type MetadataLot = - | "AudioBook" - | "Anime" - | "Book" - | "Podcast" - | "Manga" - | "Movie" - | "Show" - | "VideoGame"; - export type UserMeasurementStats = { weight?: string | null; body_mass_index?: string | null; @@ -92,12 +67,24 @@ export type UserMeasurementStats = { custom?: { [key: string]: string } | null; }; -export type ImportOrExportItemReview = { - date: string | null; - spoiler: boolean | null; - text: string | null; +export type ExportUserMeasurementItem = { + timestamp: string; + user_id: number; + name: string | null; + comment: string | null; + stats: UserMeasurementStats; }; +export type MetadataLot = + | "AudioBook" + | "Anime" + | "Book" + | "Podcast" + | "Manga" + | "Movie" + | "Show" + | "VideoGame"; + /** * Details about a specific media item that needs to be imported or exported. */ @@ -110,3 +97,27 @@ export type ImportOrExportMediaItem = { reviews: ImportOrExportItemRating[]; collections: string[]; }; + +export type ImportOrExportItemReview = { + date: string | null; + spoiler: boolean | null; + text: string | null; +}; + +export type ImportOrExportItemReviewComment = { + id: string; + text: string; + user: ReviewCommentUser; + liked_by: number[]; + created_on: string; +}; + +/** + * Details about a specific creator item that needs to be exported. + */ +export type ImportOrExportPersonItem = { + name: string; + reviews: ImportOrExportItemRating[]; +}; + +export type ReviewCommentUser = { id: number; name: string };