diff --git a/API/DTOs/SeriesDto.cs b/API/DTOs/SeriesDto.cs index 004f94b7e..59b7708cb 100644 --- a/API/DTOs/SeriesDto.cs +++ b/API/DTOs/SeriesDto.cs @@ -30,9 +30,13 @@ public class SeriesDto : IHasReadTimeEstimate /// /// Rating from logged in user. Calculated at API-time. /// - public int UserRating { get; set; } - public MangaFormat Format { get; set; } + public float UserRating { get; set; } + /// + /// If the user has set the rating or not + /// + public bool HasUserRated { get; set; } + public MangaFormat Format { get; set; } public DateTime Created { get; set; } public bool NameLocked { get; set; } diff --git a/API/DTOs/UpdateSeriesRatingDto.cs b/API/DTOs/UpdateSeriesRatingDto.cs index d1f407687..5dafa35af 100644 --- a/API/DTOs/UpdateSeriesRatingDto.cs +++ b/API/DTOs/UpdateSeriesRatingDto.cs @@ -1,9 +1,7 @@ -using System.ComponentModel.DataAnnotations; - -namespace API.DTOs; +namespace API.DTOs; public class UpdateSeriesRatingDto { public int SeriesId { get; init; } - public int UserRating { get; init; } + public float UserRating { get; init; } } diff --git a/API/Data/Migrations/20230725133536_ChangeRatingScale.Designer.cs b/API/Data/Migrations/20230725133536_ChangeRatingScale.Designer.cs new file mode 100644 index 000000000..8b5edb0ff --- /dev/null +++ b/API/Data/Migrations/20230725133536_ChangeRatingScale.Designer.cs @@ -0,0 +1,2269 @@ +// +using System; +using API.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace API.Data.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20230725133536_ChangeRatingScale")] + partial class ChangeRatingScale + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.9"); + + modelBuilder.Entity("API.Entities.AppRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("AgeRestriction") + .HasColumnType("INTEGER"); + + b.Property("AgeRestrictionIncludeUnknowns") + .HasColumnType("INTEGER"); + + b.Property("AniListAccessToken") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("ConfirmationToken") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LastActive") + .HasColumnType("TEXT"); + + b.Property("LastActiveUtc") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("API.Entities.AppUserBookmark", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("FileName") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("Page") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserBookmark"); + }); + + modelBuilder.Entity("API.Entities.AppUserOnDeckRemoval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserOnDeckRemoval"); + }); + + modelBuilder.Entity("API.Entities.AppUserPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("AutoCloseMenu") + .HasColumnType("INTEGER"); + + b.Property("BackgroundColor") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("#000000"); + + b.Property("BlurUnreadSummaries") + .HasColumnType("INTEGER"); + + b.Property("BookReaderFontFamily") + .HasColumnType("TEXT"); + + b.Property("BookReaderFontSize") + .HasColumnType("INTEGER"); + + b.Property("BookReaderImmersiveMode") + .HasColumnType("INTEGER"); + + b.Property("BookReaderLayoutMode") + .HasColumnType("INTEGER"); + + b.Property("BookReaderLineSpacing") + .HasColumnType("INTEGER"); + + b.Property("BookReaderMargin") + .HasColumnType("INTEGER"); + + b.Property("BookReaderReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("BookReaderTapToPaginate") + .HasColumnType("INTEGER"); + + b.Property("BookReaderWritingStyle") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.Property("BookThemeName") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("Dark"); + + b.Property("CollapseSeriesRelationships") + .HasColumnType("INTEGER"); + + b.Property("EmulateBook") + .HasColumnType("INTEGER"); + + b.Property("GlobalPageLayoutMode") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.Property("LayoutMode") + .HasColumnType("INTEGER"); + + b.Property("NoTransitions") + .HasColumnType("INTEGER"); + + b.Property("PageSplitOption") + .HasColumnType("INTEGER"); + + b.Property("PromptForDownloadSize") + .HasColumnType("INTEGER"); + + b.Property("ReaderMode") + .HasColumnType("INTEGER"); + + b.Property("ReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("ScalingOption") + .HasColumnType("INTEGER"); + + b.Property("ShareReviews") + .HasColumnType("INTEGER"); + + b.Property("ShowScreenHints") + .HasColumnType("INTEGER"); + + b.Property("SwipeToPaginate") + .HasColumnType("INTEGER"); + + b.Property("ThemeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId") + .IsUnique(); + + b.HasIndex("ThemeId"); + + b.ToTable("AppUserPreferences"); + }); + + modelBuilder.Entity("API.Entities.AppUserProgress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("BookScrollId") + .HasColumnType("TEXT"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("PagesRead") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("ChapterId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserProgresses"); + }); + + modelBuilder.Entity("API.Entities.AppUserRating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("HasBeenRated") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("REAL"); + + b.Property("Review") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserRating"); + }); + + modelBuilder.Entity("API.Entities.AppUserRole", b => + { + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("API.Entities.AppUserTableOfContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("BookScrollId") + .HasColumnType("TEXT"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("PageNumber") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("ChapterId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserTableOfContent"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .HasColumnType("INTEGER"); + + b.Property("AlternateCount") + .HasColumnType("INTEGER"); + + b.Property("AlternateNumber") + .HasColumnType("TEXT"); + + b.Property("AlternateSeries") + .HasColumnType("TEXT"); + + b.Property("AvgHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("ISBN") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("IsSpecial") + .HasColumnType("INTEGER"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("MaxHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MinHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("Range") + .HasColumnType("TEXT"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("SeriesGroup") + .HasColumnType("TEXT"); + + b.Property("StoryArc") + .HasColumnType("TEXT"); + + b.Property("StoryArcNumber") + .HasColumnType("TEXT"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TitleName") + .HasColumnType("TEXT"); + + b.Property("TotalCount") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.Property("WebLinks") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("WordCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("VolumeId"); + + b.ToTable("Chapter"); + }); + + modelBuilder.Entity("API.Entities.CollectionTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Promoted") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Promoted") + .IsUnique(); + + b.ToTable("CollectionTag"); + }); + + modelBuilder.Entity("API.Entities.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("EmailAddress") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LastUsed") + .HasColumnType("TEXT"); + + b.Property("LastUsedUtc") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Platform") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("Device"); + }); + + modelBuilder.Entity("API.Entities.FolderPath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastScanned") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("FolderPath"); + }); + + modelBuilder.Entity("API.Entities.Genre", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedTitle") + .IsUnique(); + + b.ToTable("Genre"); + }); + + modelBuilder.Entity("API.Entities.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowScrobbling") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("FolderWatching") + .HasColumnType("INTEGER"); + + b.Property("IncludeInDashboard") + .HasColumnType("INTEGER"); + + b.Property("IncludeInRecommended") + .HasColumnType("INTEGER"); + + b.Property("IncludeInSearch") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LastScanned") + .HasColumnType("TEXT"); + + b.Property("ManageCollections") + .HasColumnType("INTEGER"); + + b.Property("ManageReadingLists") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("API.Entities.MangaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Bytes") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Extension") + .HasColumnType("TEXT"); + + b.Property("FilePath") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("LastFileAnalysis") + .HasColumnType("TEXT"); + + b.Property("LastFileAnalysisUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.ToTable("MangaFile"); + }); + + modelBuilder.Entity("API.Entities.MediaError", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasColumnType("TEXT"); + + b.Property("Extension") + .HasColumnType("TEXT"); + + b.Property("FilePath") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("MediaError"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .HasColumnType("INTEGER"); + + b.Property("AgeRatingLocked") + .HasColumnType("INTEGER"); + + b.Property("CharacterLocked") + .HasColumnType("INTEGER"); + + b.Property("ColoristLocked") + .HasColumnType("INTEGER"); + + b.Property("CoverArtistLocked") + .HasColumnType("INTEGER"); + + b.Property("EditorLocked") + .HasColumnType("INTEGER"); + + b.Property("GenresLocked") + .HasColumnType("INTEGER"); + + b.Property("InkerLocked") + .HasColumnType("INTEGER"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LanguageLocked") + .HasColumnType("INTEGER"); + + b.Property("LettererLocked") + .HasColumnType("INTEGER"); + + b.Property("MaxCount") + .HasColumnType("INTEGER"); + + b.Property("PencillerLocked") + .HasColumnType("INTEGER"); + + b.Property("PublicationStatus") + .HasColumnType("INTEGER"); + + b.Property("PublicationStatusLocked") + .HasColumnType("INTEGER"); + + b.Property("PublisherLocked") + .HasColumnType("INTEGER"); + + b.Property("ReleaseYear") + .HasColumnType("INTEGER"); + + b.Property("ReleaseYearLocked") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("SummaryLocked") + .HasColumnType("INTEGER"); + + b.Property("TagsLocked") + .HasColumnType("INTEGER"); + + b.Property("TotalCount") + .HasColumnType("INTEGER"); + + b.Property("TranslatorLocked") + .HasColumnType("INTEGER"); + + b.Property("WebLinks") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("WriterLocked") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId") + .IsUnique(); + + b.HasIndex("Id", "SeriesId") + .IsUnique(); + + b.ToTable("SeriesMetadata"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesRelation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RelationKind") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("TargetSeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.HasIndex("TargetSeriesId"); + + b.ToTable("SeriesRelation"); + }); + + modelBuilder.Entity("API.Entities.Person", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Person"); + }); + + modelBuilder.Entity("API.Entities.ReadingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("EndingMonth") + .HasColumnType("INTEGER"); + + b.Property("EndingYear") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("NormalizedTitle") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Promoted") + .HasColumnType("INTEGER"); + + b.Property("StartingMonth") + .HasColumnType("INTEGER"); + + b.Property("StartingYear") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("ReadingList"); + }); + + modelBuilder.Entity("API.Entities.ReadingListItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("ReadingListId") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.HasIndex("ReadingListId"); + + b.HasIndex("SeriesId"); + + b.HasIndex("VolumeId"); + + b.ToTable("ReadingListItem"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleError", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("ScrobbleEventId") + .HasColumnType("INTEGER"); + + b.Property("ScrobbleEventId1") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ScrobbleEventId1"); + + b.HasIndex("SeriesId"); + + b.ToTable("ScrobbleError"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AniListId") + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ChapterNumber") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("IsProcessed") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("MalId") + .HasColumnType("INTEGER"); + + b.Property("ProcessDateUtc") + .HasColumnType("TEXT"); + + b.Property("Rating") + .HasColumnType("REAL"); + + b.Property("ReviewBody") + .HasColumnType("TEXT"); + + b.Property("ReviewTitle") + .HasColumnType("TEXT"); + + b.Property("ScrobbleEventType") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("LibraryId"); + + b.HasIndex("SeriesId"); + + b.ToTable("ScrobbleEvent"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleHold", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("ScrobbleHold"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("AvgHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("FolderPath") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("LastChapterAdded") + .HasColumnType("TEXT"); + + b.Property("LastChapterAddedUtc") + .HasColumnType("TEXT"); + + b.Property("LastFolderScanned") + .HasColumnType("TEXT"); + + b.Property("LastFolderScannedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("LocalizedName") + .HasColumnType("TEXT"); + + b.Property("LocalizedNameLocked") + .HasColumnType("INTEGER"); + + b.Property("MaxHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MinHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedLocalizedName") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("OriginalName") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("SortName") + .HasColumnType("TEXT"); + + b.Property("SortNameLocked") + .HasColumnType("INTEGER"); + + b.Property("WordCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("LibraryId"); + + b.ToTable("Series"); + }); + + modelBuilder.Entity("API.Entities.ServerSetting", b => + { + b.Property("Key") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.ToTable("ServerSetting"); + }); + + modelBuilder.Entity("API.Entities.ServerStatistics", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChapterCount") + .HasColumnType("INTEGER"); + + b.Property("FileCount") + .HasColumnType("INTEGER"); + + b.Property("GenreCount") + .HasColumnType("INTEGER"); + + b.Property("PersonCount") + .HasColumnType("INTEGER"); + + b.Property("SeriesCount") + .HasColumnType("INTEGER"); + + b.Property("TagCount") + .HasColumnType("INTEGER"); + + b.Property("UserCount") + .HasColumnType("INTEGER"); + + b.Property("VolumeCount") + .HasColumnType("INTEGER"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ServerStatistics"); + }); + + modelBuilder.Entity("API.Entities.SiteTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("FileName") + .HasColumnType("TEXT"); + + b.Property("IsDefault") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("SiteTheme"); + }); + + modelBuilder.Entity("API.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedTitle") + .IsUnique(); + + b.ToTable("Tag"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AvgHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("MaxHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MinHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("WordCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("Volume"); + }); + + modelBuilder.Entity("AppUserLibrary", b => + { + b.Property("AppUsersId") + .HasColumnType("INTEGER"); + + b.Property("LibrariesId") + .HasColumnType("INTEGER"); + + b.HasKey("AppUsersId", "LibrariesId"); + + b.HasIndex("LibrariesId"); + + b.ToTable("AppUserLibrary"); + }); + + modelBuilder.Entity("ChapterGenre", b => + { + b.Property("ChaptersId") + .HasColumnType("INTEGER"); + + b.Property("GenresId") + .HasColumnType("INTEGER"); + + b.HasKey("ChaptersId", "GenresId"); + + b.HasIndex("GenresId"); + + b.ToTable("ChapterGenre"); + }); + + modelBuilder.Entity("ChapterPerson", b => + { + b.Property("ChapterMetadatasId") + .HasColumnType("INTEGER"); + + b.Property("PeopleId") + .HasColumnType("INTEGER"); + + b.HasKey("ChapterMetadatasId", "PeopleId"); + + b.HasIndex("PeopleId"); + + b.ToTable("ChapterPerson"); + }); + + modelBuilder.Entity("ChapterTag", b => + { + b.Property("ChaptersId") + .HasColumnType("INTEGER"); + + b.Property("TagsId") + .HasColumnType("INTEGER"); + + b.HasKey("ChaptersId", "TagsId"); + + b.HasIndex("TagsId"); + + b.ToTable("ChapterTag"); + }); + + modelBuilder.Entity("CollectionTagSeriesMetadata", b => + { + b.Property("CollectionTagsId") + .HasColumnType("INTEGER"); + + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionTagsId", "SeriesMetadatasId"); + + b.HasIndex("SeriesMetadatasId"); + + b.ToTable("CollectionTagSeriesMetadata"); + }); + + modelBuilder.Entity("GenreSeriesMetadata", b => + { + b.Property("GenresId") + .HasColumnType("INTEGER"); + + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("GenresId", "SeriesMetadatasId"); + + b.HasIndex("SeriesMetadatasId"); + + b.ToTable("GenreSeriesMetadata"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("PersonSeriesMetadata", b => + { + b.Property("PeopleId") + .HasColumnType("INTEGER"); + + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("PeopleId", "SeriesMetadatasId"); + + b.HasIndex("SeriesMetadatasId"); + + b.ToTable("PersonSeriesMetadata"); + }); + + modelBuilder.Entity("SeriesMetadataTag", b => + { + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.Property("TagsId") + .HasColumnType("INTEGER"); + + b.HasKey("SeriesMetadatasId", "TagsId"); + + b.HasIndex("TagsId"); + + b.ToTable("SeriesMetadataTag"); + }); + + modelBuilder.Entity("API.Entities.AppUserBookmark", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Bookmarks") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserOnDeckRemoval", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany() + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.AppUserPreferences", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithOne("UserPreferences") + .HasForeignKey("API.Entities.AppUserPreferences", "AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.SiteTheme", "Theme") + .WithMany() + .HasForeignKey("ThemeId"); + + b.Navigation("AppUser"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("API.Entities.AppUserProgress", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Progresses") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Chapter", null) + .WithMany("UserProgress") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", null) + .WithMany("Progress") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserRating", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Ratings") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany("Ratings") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.AppUserRole", b => + { + b.HasOne("API.Entities.AppRole", "Role") + .WithMany("UserRoles") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.AppUser", "User") + .WithMany("UserRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("API.Entities.AppUserTableOfContent", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("TableOfContents") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany() + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Chapter"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.HasOne("API.Entities.Volume", "Volume") + .WithMany("Chapters") + .HasForeignKey("VolumeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Volume"); + }); + + modelBuilder.Entity("API.Entities.Device", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Devices") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.FolderPath", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("Folders") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.MangaFile", b => + { + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany("Files") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chapter"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithOne("Metadata") + .HasForeignKey("API.Entities.Metadata.SeriesMetadata", "SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesRelation", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithMany("Relations") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "TargetSeries") + .WithMany("RelationOf") + .HasForeignKey("TargetSeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + + b.Navigation("TargetSeries"); + }); + + modelBuilder.Entity("API.Entities.ReadingList", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ReadingLists") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.ReadingListItem", b => + { + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany() + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.ReadingList", "ReadingList") + .WithMany("Items") + .HasForeignKey("ReadingListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Volume", "Volume") + .WithMany() + .HasForeignKey("VolumeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chapter"); + + b.Navigation("ReadingList"); + + b.Navigation("Series"); + + b.Navigation("Volume"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleError", b => + { + b.HasOne("API.Entities.Scrobble.ScrobbleEvent", "ScrobbleEvent") + .WithMany() + .HasForeignKey("ScrobbleEventId1"); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ScrobbleEvent"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleEvent", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany() + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Library", "Library") + .WithMany() + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Library"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleHold", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ScrobbleHolds") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany("WantToRead") + .HasForeignKey("AppUserId"); + + b.HasOne("API.Entities.Library", "Library") + .WithMany("Series") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithMany("Volumes") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("AppUserLibrary", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("AppUsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Library", null) + .WithMany() + .HasForeignKey("LibrariesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ChapterGenre", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany() + .HasForeignKey("ChaptersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Genre", null) + .WithMany() + .HasForeignKey("GenresId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ChapterPerson", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany() + .HasForeignKey("ChapterMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Person", null) + .WithMany() + .HasForeignKey("PeopleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ChapterTag", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany() + .HasForeignKey("ChaptersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Tag", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("CollectionTagSeriesMetadata", b => + { + b.HasOne("API.Entities.CollectionTag", null) + .WithMany() + .HasForeignKey("CollectionTagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GenreSeriesMetadata", b => + { + b.HasOne("API.Entities.Genre", null) + .WithMany() + .HasForeignKey("GenresId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("API.Entities.AppRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PersonSeriesMetadata", b => + { + b.HasOne("API.Entities.Person", null) + .WithMany() + .HasForeignKey("PeopleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SeriesMetadataTag", b => + { + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Tag", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("API.Entities.AppRole", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Navigation("Bookmarks"); + + b.Navigation("Devices"); + + b.Navigation("Progresses"); + + b.Navigation("Ratings"); + + b.Navigation("ReadingLists"); + + b.Navigation("ScrobbleHolds"); + + b.Navigation("TableOfContents"); + + b.Navigation("UserPreferences"); + + b.Navigation("UserRoles"); + + b.Navigation("WantToRead"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.Navigation("Files"); + + b.Navigation("UserProgress"); + }); + + modelBuilder.Entity("API.Entities.Library", b => + { + b.Navigation("Folders"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.ReadingList", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.Navigation("Metadata"); + + b.Navigation("Progress"); + + b.Navigation("Ratings"); + + b.Navigation("RelationOf"); + + b.Navigation("Relations"); + + b.Navigation("Volumes"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.Navigation("Chapters"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Data/Migrations/20230725133536_ChangeRatingScale.cs b/API/Data/Migrations/20230725133536_ChangeRatingScale.cs new file mode 100644 index 000000000..4f97e008b --- /dev/null +++ b/API/Data/Migrations/20230725133536_ChangeRatingScale.cs @@ -0,0 +1,45 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace API.Data.Migrations +{ + /// + public partial class ChangeRatingScale : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Rating", + table: "AppUserRating", + type: "REAL", + nullable: false, + oldClrType: typeof(int), + oldType: "INTEGER"); + + migrationBuilder.AddColumn( + name: "HasBeenRated", + table: "AppUserRating", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "HasBeenRated", + table: "AppUserRating"); + + migrationBuilder.AlterColumn( + name: "Rating", + table: "AppUserRating", + type: "INTEGER", + nullable: false, + oldClrType: typeof(float), + oldType: "REAL"); + } + } +} diff --git a/API/Data/Migrations/DataContextModelSnapshot.cs b/API/Data/Migrations/DataContextModelSnapshot.cs index c2a9ba150..6efb397b4 100644 --- a/API/Data/Migrations/DataContextModelSnapshot.cs +++ b/API/Data/Migrations/DataContextModelSnapshot.cs @@ -15,7 +15,7 @@ namespace API.Data.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "7.0.8"); + modelBuilder.HasAnnotation("ProductVersion", "7.0.9"); modelBuilder.Entity("API.Entities.AppRole", b => { @@ -180,7 +180,7 @@ namespace API.Data.Migrations b.HasIndex("AppUserId"); - b.ToTable("AppUserBookmark", (string)null); + b.ToTable("AppUserBookmark"); }); modelBuilder.Entity("API.Entities.AppUserOnDeckRemoval", b => @@ -201,7 +201,7 @@ namespace API.Data.Migrations b.HasIndex("SeriesId"); - b.ToTable("AppUserOnDeckRemoval", (string)null); + b.ToTable("AppUserOnDeckRemoval"); }); modelBuilder.Entity("API.Entities.AppUserPreferences", b => @@ -309,7 +309,7 @@ namespace API.Data.Migrations b.HasIndex("ThemeId"); - b.ToTable("AppUserPreferences", (string)null); + b.ToTable("AppUserPreferences"); }); modelBuilder.Entity("API.Entities.AppUserProgress", b => @@ -359,7 +359,7 @@ namespace API.Data.Migrations b.HasIndex("SeriesId"); - b.ToTable("AppUserProgresses", (string)null); + b.ToTable("AppUserProgresses"); }); modelBuilder.Entity("API.Entities.AppUserRating", b => @@ -371,9 +371,12 @@ namespace API.Data.Migrations b.Property("AppUserId") .HasColumnType("INTEGER"); - b.Property("Rating") + b.Property("HasBeenRated") .HasColumnType("INTEGER"); + b.Property("Rating") + .HasColumnType("REAL"); + b.Property("Review") .HasColumnType("TEXT"); @@ -389,7 +392,7 @@ namespace API.Data.Migrations b.HasIndex("SeriesId"); - b.ToTable("AppUserRating", (string)null); + b.ToTable("AppUserRating"); }); modelBuilder.Entity("API.Entities.AppUserRole", b => @@ -457,7 +460,7 @@ namespace API.Data.Migrations b.HasIndex("SeriesId"); - b.ToTable("AppUserTableOfContent", (string)null); + b.ToTable("AppUserTableOfContent"); }); modelBuilder.Entity("API.Entities.Chapter", b => @@ -567,7 +570,7 @@ namespace API.Data.Migrations b.HasIndex("VolumeId"); - b.ToTable("Chapter", (string)null); + b.ToTable("Chapter"); }); modelBuilder.Entity("API.Entities.CollectionTag", b => @@ -602,7 +605,7 @@ namespace API.Data.Migrations b.HasIndex("Id", "Promoted") .IsUnique(); - b.ToTable("CollectionTag", (string)null); + b.ToTable("CollectionTag"); }); modelBuilder.Entity("API.Entities.Device", b => @@ -648,7 +651,7 @@ namespace API.Data.Migrations b.HasIndex("AppUserId"); - b.ToTable("Device", (string)null); + b.ToTable("Device"); }); modelBuilder.Entity("API.Entities.FolderPath", b => @@ -670,7 +673,7 @@ namespace API.Data.Migrations b.HasIndex("LibraryId"); - b.ToTable("FolderPath", (string)null); + b.ToTable("FolderPath"); }); modelBuilder.Entity("API.Entities.Genre", b => @@ -690,7 +693,7 @@ namespace API.Data.Migrations b.HasIndex("NormalizedTitle") .IsUnique(); - b.ToTable("Genre", (string)null); + b.ToTable("Genre"); }); modelBuilder.Entity("API.Entities.Library", b => @@ -748,7 +751,7 @@ namespace API.Data.Migrations b.HasKey("Id"); - b.ToTable("Library", (string)null); + b.ToTable("Library"); }); modelBuilder.Entity("API.Entities.MangaFile", b => @@ -797,7 +800,7 @@ namespace API.Data.Migrations b.HasIndex("ChapterId"); - b.ToTable("MangaFile", (string)null); + b.ToTable("MangaFile"); }); modelBuilder.Entity("API.Entities.MediaError", b => @@ -832,7 +835,7 @@ namespace API.Data.Migrations b.HasKey("Id"); - b.ToTable("MediaError", (string)null); + b.ToTable("MediaError"); }); modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b => @@ -933,7 +936,7 @@ namespace API.Data.Migrations b.HasIndex("Id", "SeriesId") .IsUnique(); - b.ToTable("SeriesMetadata", (string)null); + b.ToTable("SeriesMetadata"); }); modelBuilder.Entity("API.Entities.Metadata.SeriesRelation", b => @@ -957,7 +960,7 @@ namespace API.Data.Migrations b.HasIndex("TargetSeriesId"); - b.ToTable("SeriesRelation", (string)null); + b.ToTable("SeriesRelation"); }); modelBuilder.Entity("API.Entities.Person", b => @@ -977,7 +980,7 @@ namespace API.Data.Migrations b.HasKey("Id"); - b.ToTable("Person", (string)null); + b.ToTable("Person"); }); modelBuilder.Entity("API.Entities.ReadingList", b => @@ -1040,7 +1043,7 @@ namespace API.Data.Migrations b.HasIndex("AppUserId"); - b.ToTable("ReadingList", (string)null); + b.ToTable("ReadingList"); }); modelBuilder.Entity("API.Entities.ReadingListItem", b => @@ -1074,7 +1077,7 @@ namespace API.Data.Migrations b.HasIndex("VolumeId"); - b.ToTable("ReadingListItem", (string)null); + b.ToTable("ReadingListItem"); }); modelBuilder.Entity("API.Entities.Scrobble.ScrobbleError", b => @@ -1119,7 +1122,7 @@ namespace API.Data.Migrations b.HasIndex("SeriesId"); - b.ToTable("ScrobbleError", (string)null); + b.ToTable("ScrobbleError"); }); modelBuilder.Entity("API.Entities.Scrobble.ScrobbleEvent", b => @@ -1190,7 +1193,7 @@ namespace API.Data.Migrations b.HasIndex("SeriesId"); - b.ToTable("ScrobbleEvent", (string)null); + b.ToTable("ScrobbleEvent"); }); modelBuilder.Entity("API.Entities.Scrobble.ScrobbleHold", b => @@ -1223,7 +1226,7 @@ namespace API.Data.Migrations b.HasIndex("SeriesId"); - b.ToTable("ScrobbleHold", (string)null); + b.ToTable("ScrobbleHold"); }); modelBuilder.Entity("API.Entities.Series", b => @@ -1319,7 +1322,7 @@ namespace API.Data.Migrations b.HasIndex("LibraryId"); - b.ToTable("Series", (string)null); + b.ToTable("Series"); }); modelBuilder.Entity("API.Entities.ServerSetting", b => @@ -1336,7 +1339,7 @@ namespace API.Data.Migrations b.HasKey("Key"); - b.ToTable("ServerSetting", (string)null); + b.ToTable("ServerSetting"); }); modelBuilder.Entity("API.Entities.ServerStatistics", b => @@ -1374,7 +1377,7 @@ namespace API.Data.Migrations b.HasKey("Id"); - b.ToTable("ServerStatistics", (string)null); + b.ToTable("ServerStatistics"); }); modelBuilder.Entity("API.Entities.SiteTheme", b => @@ -1412,7 +1415,7 @@ namespace API.Data.Migrations b.HasKey("Id"); - b.ToTable("SiteTheme", (string)null); + b.ToTable("SiteTheme"); }); modelBuilder.Entity("API.Entities.Tag", b => @@ -1432,7 +1435,7 @@ namespace API.Data.Migrations b.HasIndex("NormalizedTitle") .IsUnique(); - b.ToTable("Tag", (string)null); + b.ToTable("Tag"); }); modelBuilder.Entity("API.Entities.Volume", b => @@ -1484,7 +1487,7 @@ namespace API.Data.Migrations b.HasIndex("SeriesId"); - b.ToTable("Volume", (string)null); + b.ToTable("Volume"); }); modelBuilder.Entity("AppUserLibrary", b => @@ -1499,7 +1502,7 @@ namespace API.Data.Migrations b.HasIndex("LibrariesId"); - b.ToTable("AppUserLibrary", (string)null); + b.ToTable("AppUserLibrary"); }); modelBuilder.Entity("ChapterGenre", b => @@ -1514,7 +1517,7 @@ namespace API.Data.Migrations b.HasIndex("GenresId"); - b.ToTable("ChapterGenre", (string)null); + b.ToTable("ChapterGenre"); }); modelBuilder.Entity("ChapterPerson", b => @@ -1529,7 +1532,7 @@ namespace API.Data.Migrations b.HasIndex("PeopleId"); - b.ToTable("ChapterPerson", (string)null); + b.ToTable("ChapterPerson"); }); modelBuilder.Entity("ChapterTag", b => @@ -1544,7 +1547,7 @@ namespace API.Data.Migrations b.HasIndex("TagsId"); - b.ToTable("ChapterTag", (string)null); + b.ToTable("ChapterTag"); }); modelBuilder.Entity("CollectionTagSeriesMetadata", b => @@ -1559,7 +1562,7 @@ namespace API.Data.Migrations b.HasIndex("SeriesMetadatasId"); - b.ToTable("CollectionTagSeriesMetadata", (string)null); + b.ToTable("CollectionTagSeriesMetadata"); }); modelBuilder.Entity("GenreSeriesMetadata", b => @@ -1574,7 +1577,7 @@ namespace API.Data.Migrations b.HasIndex("SeriesMetadatasId"); - b.ToTable("GenreSeriesMetadata", (string)null); + b.ToTable("GenreSeriesMetadata"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -1673,7 +1676,7 @@ namespace API.Data.Migrations b.HasIndex("SeriesMetadatasId"); - b.ToTable("PersonSeriesMetadata", (string)null); + b.ToTable("PersonSeriesMetadata"); }); modelBuilder.Entity("SeriesMetadataTag", b => @@ -1688,7 +1691,7 @@ namespace API.Data.Migrations b.HasIndex("TagsId"); - b.ToTable("SeriesMetadataTag", (string)null); + b.ToTable("SeriesMetadataTag"); }); modelBuilder.Entity("API.Entities.AppUserBookmark", b => diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index 4322181ca..9a5534b94 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -625,6 +625,7 @@ public class SeriesRepository : ISeriesRepository if (rating != null) { s.UserRating = rating.Rating; + s.HasUserRated = rating.HasBeenRated; } if (userProgress.Count > 0) @@ -1686,13 +1687,13 @@ public class SeriesRepository : ISeriesRepository { // If there is 0 or 1 rating and that rating is you, return 0 back var countOfRatingsThatAreUser = await _context.AppUserRating - .Where(r => r.SeriesId == seriesId).CountAsync(u => u.AppUserId == userId); + .Where(r => r.SeriesId == seriesId && r.HasBeenRated).CountAsync(u => u.AppUserId == userId); if (countOfRatingsThatAreUser == 1) { return 0; } var avg = (await _context.AppUserRating - .Where(r => r.SeriesId == seriesId) + .Where(r => r.SeriesId == seriesId && r.HasBeenRated) .AverageAsync(r => (int?) r.Rating)); return avg.HasValue ? (int) (avg.Value * 20) : 0; } @@ -1714,7 +1715,7 @@ public class SeriesRepository : ISeriesRepository public async Task ClearOnDeckRemoval(int seriesId, int userId) { var existingEntry = await _context.AppUserOnDeckRemoval - .Where(u => u.Id == userId && u.SeriesId == seriesId) + .Where(u => u.AppUserId == userId && u.SeriesId == seriesId) .FirstOrDefaultAsync(); if (existingEntry == null) return; _context.AppUserOnDeckRemoval.Remove(existingEntry); diff --git a/API/Entities/AppUserRating.cs b/API/Entities/AppUserRating.cs index b2d08fb7b..91734b445 100644 --- a/API/Entities/AppUserRating.cs +++ b/API/Entities/AppUserRating.cs @@ -1,13 +1,17 @@  namespace API.Entities; - +#nullable enable public class AppUserRating { public int Id { get; set; } /// - /// A number between 0-5 that represents how good a series is. + /// A number between 0-5.0 that represents how good a series is. /// - public int Rating { get; set; } + public float Rating { get; set; } + /// + /// If the rating has been explicitly set. Otherwise the 0.0 rating should be ignored as it's not rated + /// + public bool HasBeenRated { get; set; } /// /// A short summary the user can write when giving their review. /// @@ -17,7 +21,7 @@ public class AppUserRating /// public string? Tagline { get; set; } public int SeriesId { get; set; } - public Series Series { get; set; } + public Series Series { get; set; } = null!; // Relationships diff --git a/API/Services/Plus/ScrobblingService.cs b/API/Services/Plus/ScrobblingService.cs index cfb3ed9d8..c2f3477c0 100644 --- a/API/Services/Plus/ScrobblingService.cs +++ b/API/Services/Plus/ScrobblingService.cs @@ -39,7 +39,7 @@ public interface IScrobblingService { Task CheckExternalAccessTokens(); Task HasTokenExpired(int userId, ScrobbleProvider provider); - Task ScrobbleRatingUpdate(int userId, int seriesId, int rating); + Task ScrobbleRatingUpdate(int userId, int seriesId, float rating); Task ScrobbleReviewUpdate(int userId, int seriesId, string reviewTitle, string reviewBody); Task ScrobbleReadingUpdate(int userId, int seriesId); Task ScrobbleWantToReadUpdate(int userId, int seriesId, bool onWantToRead); @@ -223,7 +223,7 @@ public class ScrobblingService : IScrobblingService _logger.LogDebug("Added Scrobbling Review update on {SeriesName} with Userid {UserId} ", series.Name, userId); } - public async Task ScrobbleRatingUpdate(int userId, int seriesId, int rating) + public async Task ScrobbleRatingUpdate(int userId, int seriesId, float rating) { if (!await _licenseService.HasActiveLicense()) return; var token = await GetTokenForProvider(userId, ScrobbleProvider.AniList); diff --git a/API/Services/SeriesService.cs b/API/Services/SeriesService.cs index 66e6a57da..fcc6846d4 100644 --- a/API/Services/SeriesService.cs +++ b/API/Services/SeriesService.cs @@ -294,7 +294,8 @@ public class SeriesService : ISeriesService new AppUserRating(); try { - userRating.Rating = Math.Clamp(updateSeriesRatingDto.UserRating, 0, 5); + userRating.Rating = Math.Clamp(updateSeriesRatingDto.UserRating, 0f, 5f); + userRating.HasBeenRated = true; userRating.SeriesId = updateSeriesRatingDto.SeriesId; if (userRating.Id == 0) diff --git a/UI/Web/package-lock.json b/UI/Web/package-lock.json index f5a239173..e3ba7a6c9 100644 --- a/UI/Web/package-lock.json +++ b/UI/Web/package-lock.json @@ -36,6 +36,7 @@ "ngx-extended-pdf-viewer": "^16.2.16", "ngx-file-drop": "^16.0.0", "ngx-slider-v2": "^16.0.2", + "ngx-stars": "^1.6.5", "ngx-toastr": "^17.0.2", "rxjs": "^7.8.0", "screenfull": "^6.0.2", @@ -10564,6 +10565,18 @@ "@angular/forms": "^16.0.0" } }, + "node_modules/ngx-stars": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/ngx-stars/-/ngx-stars-1.6.5.tgz", + "integrity": "sha512-ZJ2R1XgIkBj5TsHSP8tl3QvbRBCi1awLO03Aod7ffDNG1i785ODw9gYlOAvsIrUmnY9ha1h21tTs5pBWXqA+5Q==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=2.0.0", + "@angular/core": ">=2.0.0" + } + }, "node_modules/ngx-toastr": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-17.0.2.tgz", diff --git a/UI/Web/package.json b/UI/Web/package.json index a7b9a05ee..ffa564f00 100644 --- a/UI/Web/package.json +++ b/UI/Web/package.json @@ -40,6 +40,7 @@ "ngx-extended-pdf-viewer": "^16.2.16", "ngx-file-drop": "^16.0.0", "ngx-slider-v2": "^16.0.2", + "ngx-stars": "^1.6.5", "ngx-toastr": "^17.0.2", "rxjs": "^7.8.0", "screenfull": "^6.0.2", diff --git a/UI/Web/src/app/_models/series.ts b/UI/Web/src/app/_models/series.ts index 77eed1a90..c994a3527 100644 --- a/UI/Web/src/app/_models/series.ts +++ b/UI/Web/src/app/_models/series.ts @@ -27,6 +27,7 @@ export interface Series { * User's rating (0-5) */ userRating: number; + hasUserRated: boolean; libraryId: number; /** * DateTime the entity was created diff --git a/UI/Web/src/app/cards/card-detail-drawer/card-detail-drawer.component.html b/UI/Web/src/app/cards/card-detail-drawer/card-detail-drawer.component.html index 2c2d684b8..5ed8ce116 100644 --- a/UI/Web/src/app/cards/card-detail-drawer/card-detail-drawer.component.html +++ b/UI/Web/src/app/cards/card-detail-drawer/card-detail-drawer.component.html @@ -9,16 +9,16 @@
-
@@ -63,7 +63,7 @@ - +
@@ -73,7 +73,7 @@ {{item.title}} - + @@ -98,7 +98,7 @@
  • {{tabs[TabID.Cover].title}} - - {{utilityService.formatChapterName(libraryType, true, false) }} {{formatChapterNumber(chapter)}} @@ -143,7 +143,7 @@ Pages: {{file.pages | number:''}}
    - Added: + Added: {{data.created | date: 'short' | defaultDate}} @@ -166,4 +166,4 @@
    - \ No newline at end of file + diff --git a/UI/Web/src/app/cards/entity-info-cards/entity-info-cards.component.html b/UI/Web/src/app/cards/entity-info-cards/entity-info-cards.component.html index e325170a9..62d507173 100644 --- a/UI/Web/src/app/cards/entity-info-cards/entity-info-cards.component.html +++ b/UI/Web/src/app/cards/entity-info-cards/entity-info-cards.component.html @@ -1,107 +1,121 @@ -
    - -
    - - {{chapter.releaseDate | date:'shortDate' | defaultDate}} - -
    -
    -
    - -
    - - {{chapter.ageRating | ageRating | async}} - -
    -
    -
    +
    +
    + + + {{item.title}} + - -
    - - {{totalPages | compactNumber}} Pages - -
    -
    -
    + + {{item.title}} + +
    - -
    - - {{totalWordCount | compactNumber}} Words - -
    -
    -
    +
    + +
    + + {{chapter.releaseDate | date:'shortDate' | defaultDate}} + +
    +
    +
    - -
    - - <1 Hour - - {{readingTime.minHours}}{{readingTime.maxHours !== readingTime.minHours ? ('-' + readingTime.maxHours) : ''}} Hour{{readingTime.minHours > 1 ? 's' : ''}} - - -
    -
    + +
    + + {{chapter.ageRating | ageRating | async}} + +
    +
    +
    - -
    -
    - - {{chapter.created | date:'short' | defaultDate}} - -
    -
    + +
    + + {{totalPages | compactNumber}} Pages + +
    +
    +
    - -
    -
    - - {{size | bytes}} - -
    -
    + +
    + + {{totalWordCount | compactNumber}} Words + +
    +
    +
    - -
    -
    - - {{entity.id}} - -
    - -
    -
    - - - - - -
    -
    + +
    + + <1 Hour + + {{readingTime.minHours}}{{readingTime.maxHours !== readingTime.minHours ? ('-' + readingTime.maxHours) : ''}} Hour{{readingTime.minHours > 1 ? 's' : ''}} + + +
    +
    - -
    -
    - - {{chapter.isbn}} - -
    -
    - - +
    - - {{chapter.lastReadingProgress | date: 'shortDate'}} - + + {{chapter.created | date:'short' | defaultDate}} +
    -
    -
    +
    + + +
    +
    + + {{size | bytes}} + +
    +
    + + +
    +
    + + {{entity.id}} + +
    + +
    +
    + + + + + +
    +
    + + +
    +
    + + {{chapter.isbn}} + +
    +
    + + +
    +
    + + {{chapter.lastReadingProgress | date: 'shortDate'}} + +
    +
    +
    +
    diff --git a/UI/Web/src/app/cards/entity-info-cards/entity-info-cards.component.ts b/UI/Web/src/app/cards/entity-info-cards/entity-info-cards.component.ts index 8259129df..54172094f 100644 --- a/UI/Web/src/app/cards/entity-info-cards/entity-info-cards.component.ts +++ b/UI/Web/src/app/cards/entity-info-cards/entity-info-cards.component.ts @@ -24,11 +24,13 @@ import {BytesPipe} from "../../pipe/bytes.pipe"; import {CompactNumberPipe} from "../../pipe/compact-number.pipe"; import {AgeRatingPipe} from "../../pipe/age-rating.pipe"; import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap"; +import {MetadataDetailComponent} from "../../series-detail/_components/metadata-detail/metadata-detail.component"; +import {FilterQueryParam} from "../../shared/_services/filter-utilities.service"; @Component({ selector: 'app-entity-info-cards', standalone: true, - imports: [CommonModule, IconAndTitleComponent, SafeHtmlPipe, DefaultDatePipe, BytesPipe, CompactNumberPipe, AgeRatingPipe, NgbTooltip], + imports: [CommonModule, IconAndTitleComponent, SafeHtmlPipe, DefaultDatePipe, BytesPipe, CompactNumberPipe, AgeRatingPipe, NgbTooltip, MetadataDetailComponent], templateUrl: './entity-info-cards.component.html', styleUrls: ['./entity-info-cards.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush @@ -36,6 +38,7 @@ import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap"; export class EntityInfoCardsComponent implements OnInit { @Input({required: true}) entity!: Volume | Chapter; + @Input({required: true}) libraryId!: number; /** * This will pull extra information */ @@ -75,8 +78,6 @@ export class EntityInfoCardsComponent implements OnInit { return this.chapter.webLinks.split(','); } - - constructor(private utilityService: UtilityService, private seriesService: SeriesService, private readonly cdRef: ChangeDetectorRef) {} ngOnInit(): void { @@ -127,8 +128,5 @@ export class EntityInfoCardsComponent implements OnInit { this.cdRef.markForCheck(); } - getTimezone(timezone: string): string { - const localDate = new Date(timezone); - return localDate.toLocaleString('en-US', { timeZoneName: 'short' }).split(' ')[3]; - } + protected readonly FilterQueryParam = FilterQueryParam; } diff --git a/UI/Web/src/app/cards/list-item/list-item.component.html b/UI/Web/src/app/cards/list-item/list-item.component.html index 95ca6d39c..9dc48a53d 100644 --- a/UI/Web/src/app/cards/list-item/list-item.component.html +++ b/UI/Web/src/app/cards/list-item/list-item.component.html @@ -22,7 +22,7 @@ Read - +
    {{Title}}
    @@ -30,7 +30,7 @@
    - +
    diff --git a/UI/Web/src/app/cards/list-item/list-item.component.ts b/UI/Web/src/app/cards/list-item/list-item.component.ts index fed7fc667..553590b32 100644 --- a/UI/Web/src/app/cards/list-item/list-item.component.ts +++ b/UI/Web/src/app/cards/list-item/list-item.component.ts @@ -5,15 +5,14 @@ import { EventEmitter, inject, Input, - OnDestroy, OnInit, Output } from '@angular/core'; import { ToastrService } from 'ngx-toastr'; -import { map, Observable, Subject, takeUntil } from 'rxjs'; +import { map, Observable } from 'rxjs'; import { Download } from 'src/app/shared/_models/download'; import { DownloadEvent, DownloadService } from 'src/app/shared/_services/download.service'; -import { UtilityService } from 'src/app/shared/_services/utility.service'; +import {Breakpoint, UtilityService} from 'src/app/shared/_services/utility.service'; import { Chapter } from 'src/app/_models/chapter'; import { LibraryType } from 'src/app/_models/library'; import { RelationKind } from 'src/app/_models/series-detail/relation-kind'; @@ -42,6 +41,7 @@ export class ListItemComponent implements OnInit { * Volume or Chapter to render */ @Input({required: true}) entity!: Volume | Chapter; + @Input({required: true}) libraryId!: number; /** * Image to show */ @@ -103,8 +103,14 @@ export class ListItemComponent implements OnInit { return ''; } + get ShowExtended() { + return this.utilityService.getActiveBreakpoint() === Breakpoint.Desktop; + } - constructor(private utilityService: UtilityService, private downloadService: DownloadService, + protected readonly Breakpoint = Breakpoint; + + + constructor(public utilityService: UtilityService, private downloadService: DownloadService, private toastr: ToastrService, private readonly cdRef: ChangeDetectorRef) { } ngOnInit(): void { diff --git a/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.html b/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.html index c384113a6..4cf3663f8 100644 --- a/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.html +++ b/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.html @@ -3,9 +3,10 @@ popoverTitle="Your Rating + Overall" popoverClass="md-popover"> - {{userRating * 20}} - + {{overallRating}}% - % + {{userRating * 20}} + N/A + + {{overallRating}} + % @@ -22,11 +23,9 @@ - - - - - {{userRating * 20}}% + + {{userRating * 20}}% diff --git a/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.scss b/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.scss index bf59d1ef5..0dc21384e 100644 --- a/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.scss +++ b/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.scss @@ -19,4 +19,19 @@ } } +.rating-star { + i { + position: relative; + display: inline-block; + padding-right: 0.1rem; + color: #d3d3d3; + } + .filled { + color: var(--primary-color); + + } +} +::ng-deep .star { + background-color: var(--primary-color); +} diff --git a/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.ts b/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.ts index e4b4d10a4..e1fcefd62 100644 --- a/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.ts +++ b/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.ts @@ -16,11 +16,13 @@ import {LoadingComponent} from "../../../shared/loading/loading.component"; import {AccountService} from "../../../_services/account.service"; import {LibraryType} from "../../../_models/library"; import {ProviderNamePipe} from "../../../pipe/provider-name.pipe"; +import {NgxStarsModule} from "ngx-stars"; +import {ThemeService} from "../../../_services/theme.service"; @Component({ selector: 'app-external-rating', standalone: true, - imports: [CommonModule, ProviderImagePipe, NgOptimizedImage, NgbRating, NgbPopover, LoadingComponent, ProviderNamePipe], + imports: [CommonModule, ProviderImagePipe, NgOptimizedImage, NgbRating, NgbPopover, LoadingComponent, ProviderNamePipe, NgxStarsModule], templateUrl: './external-rating.component.html', styleUrls: ['./external-rating.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, @@ -29,15 +31,19 @@ import {ProviderNamePipe} from "../../../pipe/provider-name.pipe"; export class ExternalRatingComponent implements OnInit { @Input({required: true}) seriesId!: number; @Input({required: true}) userRating!: number; + @Input({required: true}) hasUserRated!: boolean; @Input({required: true}) libraryType!: LibraryType; private readonly cdRef = inject(ChangeDetectorRef); private readonly seriesService = inject(SeriesService); private readonly accountService = inject(AccountService); + private readonly themeService = inject(ThemeService); ratings: Array = []; isLoading: boolean = false; overallRating: number = -1; + starColor = this.themeService.getCssVariable('--rating-star-color'); + ngOnInit() { @@ -58,9 +64,11 @@ export class ExternalRatingComponent implements OnInit { }); } - updateRating(rating: any) { + updateRating(rating: number) { this.seriesService.updateRating(this.seriesId, rating).subscribe(() => { this.userRating = rating; + this.hasUserRated = true; + this.cdRef.markForCheck(); }); } } diff --git a/UI/Web/src/app/series-detail/_components/metadata-detail/metadata-detail.component.html b/UI/Web/src/app/series-detail/_components/metadata-detail/metadata-detail.component.html new file mode 100644 index 000000000..2f3392f01 --- /dev/null +++ b/UI/Web/src/app/series-detail/_components/metadata-detail/metadata-detail.component.html @@ -0,0 +1,21 @@ +
    +
    +
    {{heading}}
    +
    +
    + + + + + + + + + + + + + + +
    +
    diff --git a/UI/Web/src/app/series-detail/_components/metadata-detail/metadata-detail.component.scss b/UI/Web/src/app/series-detail/_components/metadata-detail/metadata-detail.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/UI/Web/src/app/series-detail/_components/metadata-detail/metadata-detail.component.ts b/UI/Web/src/app/series-detail/_components/metadata-detail/metadata-detail.component.ts new file mode 100644 index 000000000..e4dfbb7fc --- /dev/null +++ b/UI/Web/src/app/series-detail/_components/metadata-detail/metadata-detail.component.ts @@ -0,0 +1,37 @@ +import {ChangeDetectionStrategy, Component, ContentChild, inject, Input, TemplateRef} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {A11yClickDirective} from "../../../shared/a11y-click.directive"; +import {BadgeExpanderComponent} from "../../../shared/badge-expander/badge-expander.component"; +import {TagBadgeComponent, TagBadgeCursor} from "../../../shared/tag-badge/tag-badge.component"; +import {FilterQueryParam} from "../../../shared/_services/filter-utilities.service"; +import {Router} from "@angular/router"; + +@Component({ + selector: 'app-metadata-detail', + standalone: true, + imports: [CommonModule, A11yClickDirective, BadgeExpanderComponent, TagBadgeComponent], + templateUrl: './metadata-detail.component.html', + styleUrls: ['./metadata-detail.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class MetadataDetailComponent { + + @Input({required: true}) tags: Array = []; + @Input({required: true}) libraryId!: number; + @Input({required: true}) heading!: string; + @Input() queryParam: FilterQueryParam = FilterQueryParam.None; + @ContentChild('titleTemplate') titleTemplate!: TemplateRef; + @ContentChild('itemTemplate') itemTemplate?: TemplateRef; + + private readonly router = inject(Router); + protected readonly TagBadgeCursor = TagBadgeCursor; + + + goTo(queryParamName: FilterQueryParam, filter: any) { + if (queryParamName === FilterQueryParam.None) return; + let params: any = {}; + params[queryParamName] = filter; + params[FilterQueryParam.Page] = 1; + this.router.navigate(['library', this.libraryId], {queryParams: params}); + } +} diff --git a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.html b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.html index b43fe046c..f76900b4a 100644 --- a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.html +++ b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.html @@ -174,7 +174,7 @@ - - -
    - -
    -
    -
    -
    Ratings
    -
    -
    - -
    -
    + + + + + + -
    -
    -
    Links
    -
    -
    - - - -
    -
    + + + + + + +
    -
    -
    -
    Genres
    -
    -
    - - - {{item.title}} - - -
    -
    -
    -
    -
    Tags
    -
    -
    - - - {{item.title}} - - -
    -
    -
    -
    -
    Collections
    -
    -
    - - - - {{item.title}} - - - -
    -
    -
    -
    -
    Reading Lists
    -
    -
    - - - + + {{item.title}} + + + + {{item.title}} + + + + + + {{item.title}} + + + + + + + +   (promoted) - {{item.title}} - - - -
    -
    -
    -
    -
    Writers/Authors
    -
    -
    - - - - - -
    -
    + {{item.title}} + +
    + + + + + + + + +
    -
    -
    -
    Cover Artists
    -
    -
    - - - - - -
    -
    + + + + + -
    -
    -
    Characters
    -
    -
    - - - - - -
    -
    + + + + + -
    -
    -
    Colorists
    -
    -
    - - - - - -
    -
    + + + + + -
    -
    -
    Editors
    -
    -
    - - - - - -
    -
    + + + + + -
    -
    -
    Inkers
    -
    -
    - - - - - -
    -
    + + + + + -
    -
    -
    Letterers
    -
    -
    - - - - - -
    -
    -
    -
    -
    Translators
    -
    -
    - - - - - -
    -
    + + + + + -
    -
    -
    Pencillers
    -
    -
    - - - - - -
    -
    + + + + + + + + + + + + + + + + + -
    -
    -
    Publishers
    -
    -
    - - - - - -
    -
    @@ -226,5 +128,4 @@
    - diff --git a/UI/Web/src/app/series-detail/_components/series-metadata-detail/series-metadata-detail.component.ts b/UI/Web/src/app/series-detail/_components/series-metadata-detail/series-metadata-detail.component.ts index 9da47a716..7819203f0 100644 --- a/UI/Web/src/app/series-detail/_components/series-metadata-detail/series-metadata-detail.component.ts +++ b/UI/Web/src/app/series-detail/_components/series-metadata-detail/series-metadata-detail.component.ts @@ -19,12 +19,15 @@ import {PersonBadgeComponent} from "../../../shared/person-badge/person-badge.co import {NgbCollapse} from "@ng-bootstrap/ng-bootstrap"; import {SeriesInfoCardsComponent} from "../../../cards/series-info-cards/series-info-cards.component"; import {LibraryType} from "../../../_models/library"; +import {MetadataDetailComponent} from "../metadata-detail/metadata-detail.component"; @Component({ selector: 'app-series-metadata-detail', standalone: true, - imports: [CommonModule, TagBadgeComponent, BadgeExpanderComponent, SafeHtmlPipe, ExternalRatingComponent, ReadMoreComponent, A11yClickDirective, PersonBadgeComponent, NgbCollapse, SeriesInfoCardsComponent], + imports: [CommonModule, TagBadgeComponent, BadgeExpanderComponent, SafeHtmlPipe, ExternalRatingComponent, + ReadMoreComponent, A11yClickDirective, PersonBadgeComponent, NgbCollapse, SeriesInfoCardsComponent, + MetadataDetailComponent], templateUrl: './series-metadata-detail.component.html', styleUrls: ['./series-metadata-detail.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush @@ -78,9 +81,7 @@ export class SeriesMetadataDetailComponent implements OnChanges { this.seriesMetadata.translators.length > 0; - if (this.seriesMetadata !== null) { - this.seriesSummary = (this.seriesMetadata.summary === null ? '' : this.seriesMetadata.summary).replace(/\n/g, '
    '); - } + this.seriesSummary = (this.seriesMetadata?.summary === null ? '' : this.seriesMetadata.summary).replace(/\n/g, '
    '); this.cdRef.markForCheck(); } diff --git a/UI/Web/src/app/shared/_services/filter-utilities.service.ts b/UI/Web/src/app/shared/_services/filter-utilities.service.ts index b0873eeb4..6708a6f6a 100644 --- a/UI/Web/src/app/shared/_services/filter-utilities.service.ts +++ b/UI/Web/src/app/shared/_services/filter-utilities.service.ts @@ -34,7 +34,11 @@ export enum FilterQueryParam { /** * This is a pagination control */ - Page = 'page' + Page = 'page', + /** + * Special case for the UI. Does not trigger filtering + */ + None = 'none' } @Injectable({ @@ -46,19 +50,19 @@ export class FilterUtilitiesService { /** * Updates the window location with a custom url based on filter and pagination objects - * @param pagination - * @param filter + * @param pagination + * @param filter */ updateUrlFromFilter(pagination: Pagination, filter: SeriesFilter | undefined) { const params = '?page=' + pagination.currentPage; - + const url = this.urlFromFilter(window.location.href.split('?')[0] + params, filter); window.history.replaceState(window.location.href, '', this.replacePaginationOnUrl(url, pagination)); } /** - * Patches the page query param in the window location. - * @param pagination + * Patches the page query param in the window location. + * @param pagination */ updateUrlFromPagination(pagination: Pagination) { window.history.replaceState(window.location.href, '', this.replacePaginationOnUrl(window.location.href, pagination)); @@ -127,7 +131,7 @@ export class FilterUtilitiesService { if (filter.seriesNameQuery !== '') { params += `&${FilterQueryParam.Name}=${encodeURIComponent(filter.seriesNameQuery)}`; } - + return currentUrl + params; } @@ -262,7 +266,7 @@ export class FilterUtilitiesService { anyChanged = true; } - // Rating, seriesName, + // Rating, seriesName, const rating = snapshot.queryParamMap.get(FilterQueryParam.Rating); if (rating !== undefined && rating !== null && parseInt(rating, 10) > 0) { filter.rating = parseInt(rating, 10); @@ -301,7 +305,7 @@ export class FilterUtilitiesService { filter.seriesNameQuery = decodeURIComponent(searchNameQuery); anyChanged = true; } - + return [filter, false]; // anyChanged. Testing out if having a filter active but keep drawer closed by default works better } diff --git a/UI/Web/src/theme/themes/dark.scss b/UI/Web/src/theme/themes/dark.scss index af5ad11f4..bf8aa40a7 100644 --- a/UI/Web/src/theme/themes/dark.scss +++ b/UI/Web/src/theme/themes/dark.scss @@ -252,4 +252,7 @@ --review-spoiler-bg-color: var(--primary-color); --review-spoiler-text-color: var(--body-text-color); + /** Rating Star Color **/ + --rating-star-color: var(--primary-color); + } diff --git a/openapi.json b/openapi.json index a6f6300e6..52586323c 100644 --- a/openapi.json +++ b/openapi.json @@ -11306,9 +11306,13 @@ "format": "int32" }, "rating": { - "type": "integer", - "description": "A number between 0-5 that represents how good a series is.", - "format": "int32" + "type": "number", + "description": "A number between 0-5.0 that represents how good a series is.", + "format": "float" + }, + "hasBeenRated": { + "type": "boolean", + "description": "If the rating has been explicitly set. Otherwise the 0.0 rating should be ignored as it's not rated" }, "review": { "type": "string", @@ -15362,9 +15366,13 @@ "format": "date-time" }, "userRating": { - "type": "integer", + "type": "number", "description": "Rating from logged in user. Calculated at API-time.", - "format": "int32" + "format": "float" + }, + "hasUserRated": { + "type": "boolean", + "description": "If the user has set the rating or not" }, "format": { "enum": [ @@ -17112,8 +17120,8 @@ "format": "int32" }, "userRating": { - "type": "integer", - "format": "int32" + "type": "number", + "format": "float" } }, "additionalProperties": false