From ba88848866b0a9dedb1e79b55c4d81a0fd453843 Mon Sep 17 00:00:00 2001 From: Jamie Date: Mon, 14 Feb 2022 22:08:09 +0000 Subject: [PATCH] feat(radarr): 4K Requests and Radarr 4K support * feat: updated radarr settings API to support 4k * feat: refactored the radarr setting page to support the new model * feat: Added 4k radarr to the settings page * feat: Added the new Movie 4k Request * feat: Got some of the backend rules done * feat: Made a load of progress * Removed the csproj ref * feat: fixed the radarr ui * feat: fixed up all the movie requests page * feat: Hide the 4K buttons when the user does not have the 4k permission * fix: fixed the templateref issue * test: fixed up all the tests * feat: Added migrations for media sever quality. Emby and Radarr Sync jobs now pull the quality * feat: Done the media sync jobs * feat: plex availability checker * feat: Updated the jellyfin availability checker to check for 4k * feat: updated emby availbility checker to check for 4k * feat: almost got it all working now * feat: Added 4k approve to the request list options * feat: Added 4k to the requests list and bulk approve * feat: Added the features service * feat: added feature update to the frontend * feat: got the features page working * feat: Applied the feature service on the backend * feat: added the feature flag on the UI * feat: added 4k to the card --- src/Ombi.Api.Emby/EmbyApi.cs | 2 +- .../Models/Media/EmbyMediastream.cs | 29 - .../Models/Media/Movie/EmbyMovie.cs | 1 + src/Ombi.Api.Jellyfin/JellyfinApi.cs | 2 +- .../Models/Media/JellyfinMediastream.cs | 29 - .../Models/Media/Movie/JellyfinMovie.cs | 1 + .../Models/V2/MovieResponse.cs | 7 +- .../Models/V3/RadarrV3QualityProfile.cs | 1 - .../Engine/V2/MovieRequestEngineTests.cs | 4 +- src/Ombi.Core.Tests/Engine/VoteEngineTests.cs | 6 +- .../Rule/Request/AutoApproveRuleTests.cs | 21 +- .../Rule/Request/CanRequestRuleTests.cs | 39 +- .../Request/ExistingMovieRequestRuleTests.cs | 83 +- .../Rule/Search/EmbyAvailabilityRuleTests.cs | 44 +- .../Rule/Search/ExistingRequestRuleTests.cs | 5 +- .../Search/JellyfinAvailabilityRuleTests.cs | 46 +- .../Rule/Search/RadarrCacheRuleTests.cs | 50 +- .../Engine/Interfaces/IMovieRequestEngine.cs | 6 +- .../Engine/Interfaces/IRequestEngine.cs | 6 +- src/Ombi.Core/Engine/MovieRequestEngine.cs | 169 ++- src/Ombi.Core/Engine/TvRequestEngine.cs | 6 +- .../Engine/V2/MovieSearchEngineV2.cs | 7 + src/Ombi.Core/Engine/VoteEngine.cs | 2 +- .../Models/Requests/MovieRequestViewModel.cs | 2 + .../Models/Search/SearchMovieViewModel.cs | 9 + .../Search/V2/MovieFullInfoViewModel.cs | 9 + .../Rule/Rules/Request/AutoApproveRule.cs | 33 +- .../Rule/Rules/Request/CanRequestRule.cs | 19 +- .../Rules/Request/ExistingMovieRequestRule.cs | 32 +- .../Rule/Rules/Search/EmbyAvailabilityRule.cs | 24 +- .../Rule/Rules/Search/ExistingRule.cs | 24 +- .../Rules/Search/JellyfinAvailabilityRule.cs | 22 +- .../Rule/Rules/Search/PlexAvailabilityRule.cs | 24 +- .../Rule/Rules/Search/RadarrCacheRule.cs | 13 +- src/Ombi.Core/Senders/IMovieSender.cs | 2 +- src/Ombi.Core/Senders/MovieSender.cs | 24 +- src/Ombi.Core/Services/FeatureService.cs | 28 + src/Ombi.DependencyInjection/IocExtensions.cs | 1 + src/Ombi.Helpers/OmbiRoles.cs | 1 + .../PlexContentSyncTests.cs | 78 +- .../Jobs/Emby/EmbyAvaliabilityChecker.cs | 17 +- .../Jobs/Emby/EmbyContentSync.cs | 43 +- .../Jellyfin/JellyfinAvaliabilityChecker.cs | 18 +- .../Jobs/Jellyfin/JellyfinContentSync.cs | 40 +- .../Jobs/Ombi/AutoDeleteRequests.cs | 2 +- .../Jobs/Ombi/ResendFailedRequests.cs | 4 +- .../Jobs/Plex/PlexAvailabilityChecker.cs | 34 +- .../Jobs/Plex/PlexContentSync.cs | 35 +- src/Ombi.Schedule/Jobs/Radarr/IRadarrSync.cs | 7 +- src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs | 137 +- .../Models/External/RadarrSettings.cs | 11 + .../Settings/Models/FeatureSettings.cs | 24 + src/Ombi.Store/Context/SettingsContext.cs | 1 + src/Ombi.Store/Entities/MediaServerContent.cs | 6 +- src/Ombi.Store/Entities/PlexServerContent.cs | 1 - src/Ombi.Store/Entities/RadarrCache.cs | 2 + .../Entities/Requests/MovieRequests.cs | 20 + src/Ombi.Store/MigrationHelper.cs | 24 + ...211213229_MediaServerQualities.Designer.cs | 527 +++++++ .../20220211213229_MediaServerQualities.cs | 59 + .../20220212210902_MediaServer4k.Designer.cs | 536 +++++++ .../20220212210902_MediaServer4k.cs | 48 + .../ExternalMySqlContextModelSnapshot.cs | 27 +- ...211213347_MediaServerQualities.Designer.cs | 525 +++++++ .../20220211213347_MediaServerQualities.cs | 57 + .../20220212210807_MediaServer4k.Designer.cs | 534 +++++++ .../20220212210807_MediaServer4k.cs | 48 + .../ExternalSqliteContextModelSnapshot.cs | 26 +- .../20220210195008_Radarr4kRole.Designer.cs | 1246 ++++++++++++++++ .../OmbiMySql/20220210195008_Radarr4kRole.cs | 20 + .../20220210201011_MovieRequest4K.Designer.cs | 1249 ++++++++++++++++ .../20220210201011_MovieRequest4K.cs | 26 + ...220210215019_4kMovieProperties.Designer.cs | 1273 +++++++++++++++++ .../20220210215019_4kMovieProperties.cs | 102 ++ .../OmbiMySqlContextModelSnapshot.cs | 109 +- .../20220210194758_Radarr4kRole.Designer.cs | 1244 ++++++++++++++++ .../OmbiSqlite/20220210194758_Radarr4kRole.cs | 20 + .../20220210200338_MovieRequest4K.Designer.cs | 1247 ++++++++++++++++ .../20220210200338_MovieRequest4K.cs | 26 + ...220210214920_4kMovieProperties.Designer.cs | 1271 ++++++++++++++++ .../20220210214920_4kMovieProperties.cs | 101 ++ .../OmbiSqliteContextModelSnapshot.cs | 108 +- .../Repository/EmbyContentRepository.cs | 10 +- .../IMediaServerContentRepository.cs | 1 + .../Repository/JellyfinContentRepository.cs | 6 + .../Repository/MediaServerRepository.cs | 1 + .../Repository/PlexContentRepository.cs | 5 + src/Ombi.sln | 9 + src/Ombi/ClientApp/src/app/app.module.ts | 13 +- .../actor/discover-actor.component.html | 2 +- .../actor/discover-actor.component.ts | 17 +- .../card/discover-card.component.html | 16 +- .../card/discover-card.component.scss | 4 + .../card/discover-card.component.ts | 10 +- .../carousel-list.component.html | 2 +- .../carousel-list/carousel-list.component.ts | 6 +- .../discover-collections.component.html | 2 +- .../discover-collections.component.ts | 6 +- .../search-results.component.html | 2 +- .../search-results.component.ts | 6 +- .../src/app/interfaces/IRequestModel.ts | 8 + .../app/interfaces/ISearchMovieResultV2.ts | 5 + .../ClientApp/src/app/interfaces/ISettings.ts | 5 + .../ClientApp/src/app/interfaces/IUser.ts | 5 + .../movie/movie-details.component.html | 98 +- .../movie/movie-details.component.ts | 68 +- .../movie-information-panel.component.html | 5 + .../deny-dialog/deny-dialog.component.ts | 7 +- .../shared/interfaces/interfaces.ts | 1 + .../social-icons/social-icons.component.html | 6 +- .../social-icons/social-icons.component.ts | 10 +- .../tv-requests-panel.component.ts | 2 +- .../ClientApp/src/app/pipes/QualityPipe.ts | 3 + .../albums-grid/albums-grid.component.ts | 16 +- .../movies-grid/movies-grid.component.html | 11 +- .../movies-grid/movies-grid.component.ts | 20 +- .../options/request-options.component.html | 3 + .../options/request-options.component.ts | 23 +- .../components/requests-list.component.ts | 5 +- .../components/tv-grid/tv-grid.component.ts | 16 +- .../src/app/services/feature.service.ts | 27 + .../src/app/services/requestV2.service.ts | 4 +- .../src/app/services/settings.service.ts | 7 +- .../settings/features/features.component.html | 20 + .../settings/features/features.component.scss | 5 + .../settings/features/features.component.ts | 32 + .../components/radarr-form.component.html | 101 ++ .../components/radarr-form.component.scss | 21 + .../components/radarr-form.component.ts | 92 ++ .../app/settings/radarr/radarr.component.html | 121 +- .../app/settings/radarr/radarr.component.ts | 122 +- .../src/app/settings/settings.module.ts | 101 +- .../app/settings/settingsmenu.component.html | 1 + .../shared/role-directive/role-directive.ts | 36 + .../app/shared/role-directive/role.module.ts | 8 + .../ClientApp/src/app/shared/shared.module.ts | 3 + .../customization/customization.state.ts | 1 - .../state/features/features-initializer.ts | 10 + .../app/state/features/features.actions.ts | 15 + .../src/app/state/features/features.facade.ts | 26 + .../app/state/features/features.selectors.ts | 18 + .../src/app/state/features/features.state.ts | 40 + .../ClientApp/src/app/state/features/index.ts | 4 + .../ClientApp/src/app/state/features/types.ts | 4 + .../usermanagement-user.component.ts | 20 +- src/Ombi/Controllers/V1/IdentityController.cs | 41 +- src/Ombi/Controllers/V1/RequestController.cs | 12 +- src/Ombi/Controllers/V1/SettingsController.cs | 14 +- src/Ombi/Controllers/V2/FeaturesController.cs | 88 ++ src/Ombi/Controllers/V2/RequestsController.cs | 8 +- src/Ombi/Models/MovieUpdateModel.cs | 1 + src/Ombi/Ombi.csproj | 1 + src/Ombi/wwwroot/translations/en.json | 11 + 153 files changed, 12435 insertions(+), 781 deletions(-) create mode 100644 src/Ombi.Core/Services/FeatureService.cs create mode 100644 src/Ombi.Settings/Settings/Models/FeatureSettings.cs create mode 100644 src/Ombi.Store/MigrationHelper.cs create mode 100644 src/Ombi.Store/Migrations/ExternalMySql/20220211213229_MediaServerQualities.Designer.cs create mode 100644 src/Ombi.Store/Migrations/ExternalMySql/20220211213229_MediaServerQualities.cs create mode 100644 src/Ombi.Store/Migrations/ExternalMySql/20220212210902_MediaServer4k.Designer.cs create mode 100644 src/Ombi.Store/Migrations/ExternalMySql/20220212210902_MediaServer4k.cs create mode 100644 src/Ombi.Store/Migrations/ExternalSqlite/20220211213347_MediaServerQualities.Designer.cs create mode 100644 src/Ombi.Store/Migrations/ExternalSqlite/20220211213347_MediaServerQualities.cs create mode 100644 src/Ombi.Store/Migrations/ExternalSqlite/20220212210807_MediaServer4k.Designer.cs create mode 100644 src/Ombi.Store/Migrations/ExternalSqlite/20220212210807_MediaServer4k.cs create mode 100644 src/Ombi.Store/Migrations/OmbiMySql/20220210195008_Radarr4kRole.Designer.cs create mode 100644 src/Ombi.Store/Migrations/OmbiMySql/20220210195008_Radarr4kRole.cs create mode 100644 src/Ombi.Store/Migrations/OmbiMySql/20220210201011_MovieRequest4K.Designer.cs create mode 100644 src/Ombi.Store/Migrations/OmbiMySql/20220210201011_MovieRequest4K.cs create mode 100644 src/Ombi.Store/Migrations/OmbiMySql/20220210215019_4kMovieProperties.Designer.cs create mode 100644 src/Ombi.Store/Migrations/OmbiMySql/20220210215019_4kMovieProperties.cs create mode 100644 src/Ombi.Store/Migrations/OmbiSqlite/20220210194758_Radarr4kRole.Designer.cs create mode 100644 src/Ombi.Store/Migrations/OmbiSqlite/20220210194758_Radarr4kRole.cs create mode 100644 src/Ombi.Store/Migrations/OmbiSqlite/20220210200338_MovieRequest4K.Designer.cs create mode 100644 src/Ombi.Store/Migrations/OmbiSqlite/20220210200338_MovieRequest4K.cs create mode 100644 src/Ombi.Store/Migrations/OmbiSqlite/20220210214920_4kMovieProperties.Designer.cs create mode 100644 src/Ombi.Store/Migrations/OmbiSqlite/20220210214920_4kMovieProperties.cs create mode 100644 src/Ombi/ClientApp/src/app/services/feature.service.ts create mode 100644 src/Ombi/ClientApp/src/app/settings/features/features.component.html create mode 100644 src/Ombi/ClientApp/src/app/settings/features/features.component.scss create mode 100644 src/Ombi/ClientApp/src/app/settings/features/features.component.ts create mode 100644 src/Ombi/ClientApp/src/app/settings/radarr/components/radarr-form.component.html create mode 100644 src/Ombi/ClientApp/src/app/settings/radarr/components/radarr-form.component.scss create mode 100644 src/Ombi/ClientApp/src/app/settings/radarr/components/radarr-form.component.ts create mode 100644 src/Ombi/ClientApp/src/app/shared/role-directive/role-directive.ts create mode 100644 src/Ombi/ClientApp/src/app/shared/role-directive/role.module.ts create mode 100644 src/Ombi/ClientApp/src/app/state/features/features-initializer.ts create mode 100644 src/Ombi/ClientApp/src/app/state/features/features.actions.ts create mode 100644 src/Ombi/ClientApp/src/app/state/features/features.facade.ts create mode 100644 src/Ombi/ClientApp/src/app/state/features/features.selectors.ts create mode 100644 src/Ombi/ClientApp/src/app/state/features/features.state.ts create mode 100644 src/Ombi/ClientApp/src/app/state/features/index.ts create mode 100644 src/Ombi/ClientApp/src/app/state/features/types.ts create mode 100644 src/Ombi/Controllers/V2/FeaturesController.cs diff --git a/src/Ombi.Api.Emby/EmbyApi.cs b/src/Ombi.Api.Emby/EmbyApi.cs index d911ca212..c12cdb716 100644 --- a/src/Ombi.Api.Emby/EmbyApi.cs +++ b/src/Ombi.Api.Emby/EmbyApi.cs @@ -170,7 +170,7 @@ namespace Ombi.Api.Emby request.AddQueryString("Recursive", true.ToString()); request.AddQueryString("IncludeItemTypes", type); - request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds"); + request.AddQueryString("Fields", includeOverview ? "ProviderIds,MediaStreams,Overview" : "ProviderIds,MediaStreams "); request.AddQueryString("startIndex", startIndex.ToString()); request.AddQueryString("limit", count.ToString()); request.AddQueryString("sortBy", "DateCreated"); diff --git a/src/Ombi.Api.Emby/Models/Media/EmbyMediastream.cs b/src/Ombi.Api.Emby/Models/Media/EmbyMediastream.cs index 01032e0e7..f633f4ce0 100644 --- a/src/Ombi.Api.Emby/Models/Media/EmbyMediastream.cs +++ b/src/Ombi.Api.Emby/Models/Media/EmbyMediastream.cs @@ -2,35 +2,6 @@ namespace Ombi.Api.Emby.Models.Movie { public class EmbyMediastream { - public string Codec { get; set; } - public string Language { get; set; } - public string TimeBase { get; set; } - public string CodecTimeBase { get; set; } - public string NalLengthSize { get; set; } - public bool IsInterlaced { get; set; } - public bool IsAVC { get; set; } - public int BitRate { get; set; } - public int BitDepth { get; set; } - public int RefFrames { get; set; } - public bool IsDefault { get; set; } - public bool IsForced { get; set; } - public int Height { get; set; } - public int Width { get; set; } - public float AverageFrameRate { get; set; } - public float RealFrameRate { get; set; } - public string Profile { get; set; } - public string Type { get; set; } - public string AspectRatio { get; set; } - public int Index { get; set; } - public bool IsExternal { get; set; } - public bool IsTextSubtitleStream { get; set; } - public bool SupportsExternalStream { get; set; } - public string PixelFormat { get; set; } - public int Level { get; set; } - public bool IsAnamorphic { get; set; } public string DisplayTitle { get; set; } - public string ChannelLayout { get; set; } - public int Channels { get; set; } - public int SampleRate { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Api.Emby/Models/Media/Movie/EmbyMovie.cs b/src/Ombi.Api.Emby/Models/Media/Movie/EmbyMovie.cs index a10ddaae6..e127f75f6 100644 --- a/src/Ombi.Api.Emby/Models/Media/Movie/EmbyMovie.cs +++ b/src/Ombi.Api.Emby/Models/Media/Movie/EmbyMovie.cs @@ -30,5 +30,6 @@ namespace Ombi.Api.Emby.Models.Movie public int CriticRating { get; set; } public string Overview { get; set; } public EmbyProviderids ProviderIds { get; set; } + public EmbyMediastream[] MediaStreams { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Api.Jellyfin/JellyfinApi.cs b/src/Ombi.Api.Jellyfin/JellyfinApi.cs index 2fafbfd86..f6afb7912 100644 --- a/src/Ombi.Api.Jellyfin/JellyfinApi.cs +++ b/src/Ombi.Api.Jellyfin/JellyfinApi.cs @@ -157,7 +157,7 @@ namespace Ombi.Api.Jellyfin request.AddQueryString("Recursive", true.ToString()); request.AddQueryString("IncludeItemTypes", type); - request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview,ParentId" : "ProviderIds,ParentId"); + request.AddQueryString("Fields", includeOverview ? "ProviderIds,MediaStreams Overview,ParentId" : "ProviderIds,ParentId,MediaStreams"); request.AddQueryString("startIndex", startIndex.ToString()); request.AddQueryString("limit", count.ToString()); if(!string.IsNullOrEmpty(parentIdFilder)) diff --git a/src/Ombi.Api.Jellyfin/Models/Media/JellyfinMediastream.cs b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinMediastream.cs index 89da2651a..e15a21e7b 100644 --- a/src/Ombi.Api.Jellyfin/Models/Media/JellyfinMediastream.cs +++ b/src/Ombi.Api.Jellyfin/Models/Media/JellyfinMediastream.cs @@ -2,35 +2,6 @@ namespace Ombi.Api.Jellyfin.Models.Movie { public class JellyfinMediastream { - public string Codec { get; set; } - public string Language { get; set; } - public string TimeBase { get; set; } - public string CodecTimeBase { get; set; } - public string NalLengthSize { get; set; } - public bool IsInterlaced { get; set; } - public bool IsAVC { get; set; } - public int BitRate { get; set; } - public int BitDepth { get; set; } - public int RefFrames { get; set; } - public bool IsDefault { get; set; } - public bool IsForced { get; set; } - public int Height { get; set; } - public int Width { get; set; } - public float AverageFrameRate { get; set; } - public float RealFrameRate { get; set; } - public string Profile { get; set; } - public string Type { get; set; } - public string AspectRatio { get; set; } - public int Index { get; set; } - public bool IsExternal { get; set; } - public bool IsTextSubtitleStream { get; set; } - public bool SupportsExternalStream { get; set; } - public string PixelFormat { get; set; } - public int Level { get; set; } - public bool IsAnamorphic { get; set; } public string DisplayTitle { get; set; } - public string ChannelLayout { get; set; } - public int Channels { get; set; } - public int SampleRate { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Api.Jellyfin/Models/Media/Movie/JellyfinMovie.cs b/src/Ombi.Api.Jellyfin/Models/Media/Movie/JellyfinMovie.cs index d86bf5047..a83e1f087 100644 --- a/src/Ombi.Api.Jellyfin/Models/Media/Movie/JellyfinMovie.cs +++ b/src/Ombi.Api.Jellyfin/Models/Media/Movie/JellyfinMovie.cs @@ -30,5 +30,6 @@ namespace Ombi.Api.Jellyfin.Models.Movie public int CriticRating { get; set; } public string Overview { get; set; } public JellyfinProviderids ProviderIds { get; set; } + public JellyfinMediastream[] MediaStreams { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Api.Radarr/Models/V2/MovieResponse.cs b/src/Ombi.Api.Radarr/Models/V2/MovieResponse.cs index 6eb2f1c5a..15627d414 100644 --- a/src/Ombi.Api.Radarr/Models/V2/MovieResponse.cs +++ b/src/Ombi.Api.Radarr/Models/V2/MovieResponse.cs @@ -44,7 +44,10 @@ namespace Ombi.Api.Radarr.Models public int id { get; set; } } - + public class MovieQuality + { + public V3.Quality quality { get; set; } + } public class Moviefile { public int movieId { get; set; } @@ -54,7 +57,7 @@ namespace Ombi.Api.Radarr.Models public DateTime dateAdded { get; set; } public string sceneName { get; set; } public int indexerFlags { get; set; } - public V3.Quality quality { get; set; } + public MovieQuality quality { get; set; } public Mediainfo mediaInfo { get; set; } public string originalFilePath { get; set; } public bool qualityCutoffNotMet { get; set; } diff --git a/src/Ombi.Api.Radarr/Models/V3/RadarrV3QualityProfile.cs b/src/Ombi.Api.Radarr/Models/V3/RadarrV3QualityProfile.cs index d985da358..6eca4af87 100644 --- a/src/Ombi.Api.Radarr/Models/V3/RadarrV3QualityProfile.cs +++ b/src/Ombi.Api.Radarr/Models/V3/RadarrV3QualityProfile.cs @@ -25,5 +25,4 @@ public int resolution { get; set; } public string modifier { get; set; } } - } diff --git a/src/Ombi.Core.Tests/Engine/V2/MovieRequestEngineTests.cs b/src/Ombi.Core.Tests/Engine/V2/MovieRequestEngineTests.cs index 3c651b167..d9f5b11be 100644 --- a/src/Ombi.Core.Tests/Engine/V2/MovieRequestEngineTests.cs +++ b/src/Ombi.Core.Tests/Engine/V2/MovieRequestEngineTests.cs @@ -9,6 +9,7 @@ using Ombi.Api.TheMovieDb; using Ombi.Core.Engine; using Ombi.Core.Models.Requests; using Ombi.Core.Rule.Interfaces; +using Ombi.Core.Services; using Ombi.Core.Settings; using Ombi.Helpers; using Ombi.Settings.Settings.Models; @@ -43,8 +44,9 @@ namespace Ombi.Core.Tests.Engine.V2 var ombiSettings = new Mock>(); var requestSubs = new Mock>(); var mediaCache = new Mock(); + var featureService = new Mock(); _engine = new MovieRequestEngine(movieApi.Object, requestService.Object, user.Object, notificationHelper.Object, rules.Object, movieSender.Object, - logger.Object, userManager.Object, requestLogRepo.Object, cache.Object, ombiSettings.Object, requestSubs.Object, mediaCache.Object); + logger.Object, userManager.Object, requestLogRepo.Object, cache.Object, ombiSettings.Object, requestSubs.Object, mediaCache.Object, featureService.Object); } [Test] diff --git a/src/Ombi.Core.Tests/Engine/VoteEngineTests.cs b/src/Ombi.Core.Tests/Engine/VoteEngineTests.cs index be874669c..1269cd2bc 100644 --- a/src/Ombi.Core.Tests/Engine/VoteEngineTests.cs +++ b/src/Ombi.Core.Tests/Engine/VoteEngineTests.cs @@ -83,7 +83,7 @@ namespace Ombi.Core.Tests.Engine Assert.That(result.Result, Is.True); VoteRepository.Verify(x => x.Add(It.Is(c => c.UserId == "abc" && c.VoteType == type)), Times.Once); VoteRepository.Verify(x => x.Delete(It.IsAny()), Times.Never); - MovieRequestEngine.Verify(x => x.ApproveMovieById(1), Times.Never); + MovieRequestEngine.Verify(x => x.ApproveMovieById(1, false), Times.Never); } public static IEnumerable VoteData { @@ -129,7 +129,7 @@ namespace Ombi.Core.Tests.Engine Assert.That(result.Result, Is.False); VoteRepository.Verify(x => x.Delete(It.IsAny()), Times.Never); - MovieRequestEngine.Verify(x => x.ApproveMovieById(1), Times.Never); + MovieRequestEngine.Verify(x => x.ApproveMovieById(1, false), Times.Never); } public static IEnumerable AttemptedTwiceData { @@ -175,7 +175,7 @@ namespace Ombi.Core.Tests.Engine Assert.That(result.Result, Is.True); VoteRepository.Verify(x => x.Delete(It.IsAny()), Times.Once); VoteRepository.Verify(x => x.Add(It.Is(v => v.VoteType == type)), Times.Once); - MovieRequestEngine.Verify(x => x.ApproveMovieById(1), Times.Never); + MovieRequestEngine.Verify(x => x.ApproveMovieById(1, false), Times.Never); } public static IEnumerable VoteConvertData { diff --git a/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs b/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs index 595ba39e0..9633249d4 100644 --- a/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs @@ -10,6 +10,7 @@ using Ombi.Test.Common; using System.Collections.Generic; using Ombi.Store.Entities; using System; +using Ombi.Core.Services; namespace Ombi.Core.Tests.Rule.Request { @@ -28,21 +29,23 @@ namespace Ombi.Core.Tests.Rule.Request PrincipalMock = new Mock(); PrincipalMock.Setup(x => x.Identity.Name).Returns("abc"); + FeatureService = new Mock(); UserManager = MockHelper.MockUserManager(_users); - Rule = new AutoApproveRule(PrincipalMock.Object, UserManager.Object); + Rule = new AutoApproveRule(PrincipalMock.Object, UserManager.Object, FeatureService.Object); } private AutoApproveRule Rule { get; set; } private Mock PrincipalMock { get; set; } private Mock UserManager { get; set; } + private Mock FeatureService { get; set; } [Test] public async Task Should_ReturnSuccess_WhenAdminAndRequestMovie() { UserManager.Setup(x => x.IsInRoleAsync(It.IsAny(), OmbiRoles.Admin)).ReturnsAsync(true); - var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie }; + var request = new MovieRequests() { RequestType = Store.Entities.RequestType.Movie }; var result = await Rule.Execute(request); Assert.True(result.Success); @@ -64,7 +67,7 @@ namespace Ombi.Core.Tests.Rule.Request public async Task Should_ReturnSuccess_WhenAutoApproveMovieAndRequestMovie() { UserManager.Setup(x => x.IsInRoleAsync(It.IsAny(), OmbiRoles.AutoApproveMovie)).ReturnsAsync(true); - var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie }; + var request = new MovieRequests() { RequestType = Store.Entities.RequestType.Movie }; var result = await Rule.Execute(request); Assert.True(result.Success); @@ -137,5 +140,17 @@ namespace Ombi.Core.Tests.Rule.Request Assert.True(result.Success); Assert.False(request.Approved); } + + [Test] + public async Task Should_ReturnFail_When4kRequestAndFeatureNotEnabled() + { + UserManager.Setup(x => x.IsInRoleAsync(It.IsAny(), It.IsAny())).ReturnsAsync(false); + var request = new MovieRequests() { RequestType = Store.Entities.RequestType.Movie, Is4kRequest = true }; + var result = await Rule.Execute(request); + + Assert.True(result.Success); + Assert.False(request.Approved); + Assert.False(request.Approved4K); + } } } diff --git a/src/Ombi.Core.Tests/Rule/Request/CanRequestRuleTests.cs b/src/Ombi.Core.Tests/Rule/Request/CanRequestRuleTests.cs index 03dc6f68c..f601689dc 100644 --- a/src/Ombi.Core.Tests/Rule/Request/CanRequestRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Request/CanRequestRuleTests.cs @@ -42,7 +42,42 @@ namespace Ombi.Core.Tests.Rule.Request public async Task Should_ReturnSuccess_WhenRequestingMovieWithMovieRole() { UserManager.Setup(x => x.IsInRoleAsync(It.IsAny(), OmbiRoles.RequestMovie)).ReturnsAsync(true); - var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie }; + var request = new MovieRequests() { RequestType = Store.Entities.RequestType.Movie }; + var result = await Rule.Execute(request); + + Assert.True(result.Success); + } + + [Test] + public async Task Should_ReturnSuccess_WhenRequestingMovie4KWithMovieRole() + { + UserManager.Setup(x => x.IsInRoleAsync(It.IsAny(), OmbiRoles.RequestMovie)).ReturnsAsync(true); + UserManager.Setup(x => x.IsInRoleAsync(It.IsAny(), OmbiRoles.Request4KMovie)).ReturnsAsync(true); + var request = new MovieRequests() { RequestType = Store.Entities.RequestType.Movie, Has4KRequest = true }; + var result = await Rule.Execute(request); + + Assert.True(result.Success); + } + + [Test] + public async Task Should_ReturnFailure_WhenRequestingMovie4KWithMovieRole() + { + UserManager.Setup(x => x.IsInRoleAsync(It.IsAny(), OmbiRoles.RequestMovie)).ReturnsAsync(true); + UserManager.Setup(x => x.IsInRoleAsync(It.IsAny(), OmbiRoles.Request4KMovie)).ReturnsAsync(false); + var request = new MovieRequests() { RequestType = Store.Entities.RequestType.Movie, Is4kRequest = true }; + var result = await Rule.Execute(request); + + Assert.False(result.Success); + Assert.False(string.IsNullOrEmpty(result.Message)); + } + + [Test] + public async Task Should_ReturnSuccess_WhenRequestingMovie4KWithAutoApprove() + { + UserManager.Setup(x => x.IsInRoleAsync(It.IsAny(), OmbiRoles.RequestMovie)).ReturnsAsync(true); + UserManager.Setup(x => x.IsInRoleAsync(It.IsAny(), OmbiRoles.AutoApproveMovie)).ReturnsAsync(true); + UserManager.Setup(x => x.IsInRoleAsync(It.IsAny(), OmbiRoles.Request4KMovie)).ReturnsAsync(false); + var request = new MovieRequests() { RequestType = Store.Entities.RequestType.Movie, Has4KRequest = true }; var result = await Rule.Execute(request); Assert.True(result.Success); @@ -52,7 +87,7 @@ namespace Ombi.Core.Tests.Rule.Request public async Task Should_ReturnFail_WhenRequestingMovieWithoutMovieRole() { UserManager.Setup(x => x.IsInRoleAsync(It.IsAny(), OmbiRoles.RequestMovie)).ReturnsAsync(false); - var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie }; + var request = new MovieRequests() { RequestType = Store.Entities.RequestType.Movie }; var result = await Rule.Execute(request); Assert.False(result.Success); diff --git a/src/Ombi.Core.Tests/Rule/Request/ExistingMovieRequestRuleTests.cs b/src/Ombi.Core.Tests/Rule/Request/ExistingMovieRequestRuleTests.cs index 7ff69c9f2..682d4bf4d 100644 --- a/src/Ombi.Core.Tests/Rule/Request/ExistingMovieRequestRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Request/ExistingMovieRequestRuleTests.cs @@ -9,7 +9,9 @@ using NUnit.Framework; using Ombi.Core.Authentication; using Ombi.Core.Rule.Rules; using Ombi.Core.Rule.Rules.Request; +using Ombi.Core.Services; using Ombi.Helpers; +using Ombi.Settings.Settings.Models; using Ombi.Store.Entities; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository.Requests; @@ -24,12 +26,14 @@ namespace Ombi.Core.Tests.Rule.Request public void Setup() { ContextMock = new Mock(); - Rule = new ExistingMovieRequestRule(ContextMock.Object); + FeatureService = new Mock(); + Rule = new ExistingMovieRequestRule(ContextMock.Object, FeatureService.Object); } private ExistingMovieRequestRule Rule { get; set; } private Mock ContextMock { get; set; } + private Mock FeatureService { get; set; } [Test] public async Task ExistingRequestRule_Movie_Has_Been_Requested_With_TheMovieDBId() @@ -96,5 +100,82 @@ namespace Ombi.Core.Tests.Rule.Request Assert.That(result.Success, Is.True); Assert.That(result.Message, Is.Null.Or.Empty); } + + [Test] + public async Task ExistingRequestRule_Movie_HasAlready4K_Request() + { + ContextMock.Setup(x => x.GetAll()).Returns(new List + { + new MovieRequests + { + TheMovieDbId = 2, + ImdbId = "2", + RequestType = RequestType.Movie, + Is4kRequest = true + } + }.AsQueryable().BuildMock().Object); + var o = new MovieRequests + { + TheMovieDbId = 2, + ImdbId = "1", + Has4KRequest = true + }; + var result = await Rule.Execute(o); + + Assert.That(result.Success, Is.False); + Assert.That(result.Message, Is.Not.Empty); + } + + [Test] + public async Task ExistingRequestRule_Movie_4K_Request() + { + FeatureService.Setup(x => x.FeatureEnabled(FeatureNames.Movie4KRequests)).ReturnsAsync(true); + ContextMock.Setup(x => x.GetAll()).Returns(new List + { + new MovieRequests + { + TheMovieDbId = 2, + ImdbId = "2", + RequestType = RequestType.Movie, + Is4kRequest = false + } + }.AsQueryable().BuildMock().Object); + var o = new MovieRequests + { + TheMovieDbId = 2, + ImdbId = "1", + Is4kRequest = true + }; + var result = await Rule.Execute(o); + + Assert.That(result.Success, Is.True); + Assert.That(result.Message, Is.Null.Or.Empty); + } + + [Test] + public async Task ExistingRequestRule_Movie_4K_Request_FeatureNotEnabled() + { + FeatureService.Setup(x => x.FeatureEnabled(FeatureNames.Movie4KRequests)).ReturnsAsync(false); + ContextMock.Setup(x => x.GetAll()).Returns(new List + { + new MovieRequests + { + TheMovieDbId = 2, + ImdbId = "2", + RequestType = RequestType.Movie, + Is4kRequest = false + } + }.AsQueryable().BuildMock().Object); + var o = new MovieRequests + { + TheMovieDbId = 2, + ImdbId = "1", + Is4kRequest = true + }; + var result = await Rule.Execute(o); + + Assert.That(result.Success, Is.False); + Assert.That(result.Message, Is.Not.Null); + } } } diff --git a/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs index 0e455418a..95be538cd 100644 --- a/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs @@ -35,7 +35,48 @@ namespace Ombi.Core.Tests.Rule.Search SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new EmbySettings()); ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny())).ReturnsAsync(new EmbyContent { - ProviderId = "123" + TheMovieDbId = "123", + Quality = "1" + }); + var search = new SearchMovieViewModel() + { + TheMovieDbId = "123", + }; + var result = await Rule.Execute(search); + + Assert.True(result.Success); + Assert.True(search.Available); + } + + [Test] + public async Task Movie_ShouldBe_Available_WhenFoundInEmby_4K() + { + SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new EmbySettings()); + ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny())).ReturnsAsync(new EmbyContent + { + TheMovieDbId = "123", + Has4K = true + }); + var search = new SearchMovieViewModel() + { + TheMovieDbId = "123", + }; + var result = await Rule.Execute(search); + + Assert.True(result.Success); + Assert.True(search.Available4K); + Assert.False(search.Available); + } + + [Test] + public async Task Movie_ShouldBe_Available_WhenFoundInEmby_Both() + { + SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new EmbySettings()); + ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny())).ReturnsAsync(new EmbyContent + { + TheMovieDbId = "123", + Has4K = true, + Quality = "1" }); var search = new SearchMovieViewModel() { @@ -44,6 +85,7 @@ namespace Ombi.Core.Tests.Rule.Search var result = await Rule.Execute(search); Assert.True(result.Success); + Assert.True(search.Available4K); Assert.True(search.Available); } diff --git a/src/Ombi.Core.Tests/Rule/Search/ExistingRequestRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/ExistingRequestRuleTests.cs index 11860ce28..66601f936 100644 --- a/src/Ombi.Core.Tests/Rule/Search/ExistingRequestRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/ExistingRequestRuleTests.cs @@ -30,13 +30,14 @@ namespace Ombi.Core.Tests.Rule.Search [Test] - public async Task ShouldBe_Requested_WhenExisitngMovie() + public async Task ShouldBe_Requested_WhenExistingMovie() { var list = new MovieRequests { TheMovieDbId = 123, Approved = true, - RequestType = RequestType.Movie + RequestType = RequestType.Movie, + RequestedDate = System.DateTime.Now, }; MovieMock.Setup(x => x.GetRequestAsync(123)).ReturnsAsync(list); diff --git a/src/Ombi.Core.Tests/Rule/Search/JellyfinAvailabilityRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/JellyfinAvailabilityRuleTests.cs index b733b0b2b..c0034afb7 100644 --- a/src/Ombi.Core.Tests/Rule/Search/JellyfinAvailabilityRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/JellyfinAvailabilityRuleTests.cs @@ -35,7 +35,48 @@ namespace Ombi.Core.Tests.Rule.Search SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new JellyfinSettings()); ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny())).ReturnsAsync(new JellyfinContent { - ProviderId = "123" + TheMovieDbId = "123", + Quality = "1080" + }); + var search = new SearchMovieViewModel() + { + TheMovieDbId = "123", + }; + var result = await Rule.Execute(search); + + Assert.True(result.Success); + Assert.True(search.Available); + } + + [Test] + public async Task Movie_ShouldBe_Available_WhenFoundInJellyfin_4K() + { + SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new JellyfinSettings()); + ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny())).ReturnsAsync(new JellyfinContent + { + TheMovieDbId = "123", + Has4K = true + }); + var search = new SearchMovieViewModel() + { + TheMovieDbId = "123", + }; + var result = await Rule.Execute(search); + + Assert.True(result.Success); + Assert.False(search.Available); + Assert.True(search.Available4K); + } + + [Test] + public async Task Movie_ShouldBe_Available_WhenFoundInJellyfin_Both() + { + SettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new JellyfinSettings()); + ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny())).ReturnsAsync(new JellyfinContent + { + TheMovieDbId = "123", + Has4K = true, + Quality = "1" }); var search = new SearchMovieViewModel() { @@ -45,6 +86,7 @@ namespace Ombi.Core.Tests.Rule.Search Assert.True(result.Success); Assert.True(search.Available); + Assert.True(search.Available4K); } [Test] @@ -66,7 +108,7 @@ namespace Ombi.Core.Tests.Rule.Search }); ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny())).ReturnsAsync(new JellyfinContent { - ProviderId = "123", + TheMovieDbId = "123", JellyfinId = 1.ToString() }); var search = new SearchMovieViewModel() diff --git a/src/Ombi.Core.Tests/Rule/Search/RadarrCacheRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/RadarrCacheRuleTests.cs index 94efe89a2..8c5e0c662 100644 --- a/src/Ombi.Core.Tests/Rule/Search/RadarrCacheRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/RadarrCacheRuleTests.cs @@ -28,25 +28,67 @@ namespace Ombi.Core.Tests.Rule.Search { var list = new List(){new RadarrCache { - TheMovieDbId = 123 + TheMovieDbId = 123, + HasRegular = true }}.AsQueryable(); - + ContextMock.Setup(x => x.GetAll()).Returns(list); var request = new SearchMovieViewModel { Id = 123 }; - var result =await Rule.Execute(request); + var result = await Rule.Execute(request); Assert.True(result.Success); Assert.True(request.Approved); } + [Test] + public async Task Should_ReturnAvailabl_WhenMovieIsInRadarr_4K() + { + var list = new List(){new RadarrCache + { + TheMovieDbId = 123, + Has4K = true, + HasFile = true + }}.AsQueryable(); + + ContextMock.Setup(x => x.GetAll()).Returns(list); + + var request = new SearchMovieViewModel { Id = 123 }; + var result = await Rule.Execute(request); + + Assert.True(result.Success); + Assert.False(request.Available); + Assert.True(request.Available4K); + } + + [Test] + public async Task Should_ReturnAvailable_WhenMovieIsInRadarr_Both() + { + var list = new List(){new RadarrCache + { + TheMovieDbId = 123, + Has4K = true, + HasRegular = true, + HasFile = true + }}.AsQueryable(); + + ContextMock.Setup(x => x.GetAll()).Returns(list); + + var request = new SearchMovieViewModel { Id = 123 }; + var result = await Rule.Execute(request); + + Assert.True(result.Success); + Assert.True(request.Available); + Assert.True(request.Available4K); + + } [Test] public async Task Should_ReturnNotApproved_WhenMovieIsNotInRadarr() { var list = DbHelper.GetQueryableMockDbSet(new RadarrCache { - TheMovieDbId = 000012 + TheMovieDbId = 000012, }); ContextMock.Setup(x => x.GetAll()).Returns(list); diff --git a/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs index 7cc64bee2..ab7a9079a 100644 --- a/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs @@ -18,9 +18,9 @@ namespace Ombi.Core.Engine.Interfaces Task RemoveAllMovieRequests(); Task GetRequest(int requestId); Task UpdateMovieRequest(MovieRequests request); - Task ApproveMovie(MovieRequests request); - Task ApproveMovieById(int requestId); - Task DenyMovieById(int modelId, string denyReason); + Task ApproveMovie(MovieRequests request, bool is4K); + Task ApproveMovieById(int requestId, bool is4K); + Task DenyMovieById(int modelId, string denyReason, bool is4K); Task> GetRequests(int count, int position, string sortProperty, string sortOrder); Task> GetUnavailableRequests(int count, int position, string sortProperty, diff --git a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs index 44278753f..d0fc04b10 100644 --- a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs @@ -19,11 +19,11 @@ namespace Ombi.Core.Engine.Interfaces Task> GetRequests(); Task UserHasRequest(string userId); - Task MarkUnavailable(int modelId); - Task MarkAvailable(int modelId); + Task MarkUnavailable(int modelId, bool is4K); + Task MarkAvailable(int modelId, bool is4K); Task GetTotal(); Task UnSubscribeRequest(int requestId, RequestType type); Task SubscribeToRequest(int requestId, RequestType type); - Task ReProcessRequest(int requestId, CancellationToken cancellationToken); + Task ReProcessRequest(int requestId, bool is4K, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index f69e890be..2a29196b2 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -22,6 +22,7 @@ using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; using Ombi.Core.Models; using System.Threading; +using Ombi.Core.Services; namespace Ombi.Core.Engine { @@ -30,7 +31,8 @@ namespace Ombi.Core.Engine public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestService, IPrincipal user, INotificationHelper helper, IRuleEvaluator r, IMovieSender sender, ILogger log, OmbiUserManager manager, IRepository rl, ICacheService cache, - ISettingsService ombiSettings, IRepository sub, IMediaCacheService mediaCacheService) + ISettingsService ombiSettings, IRepository sub, IMediaCacheService mediaCacheService, + IFeatureService featureService) : base(user, requestService, r, manager, cache, ombiSettings, sub) { MovieApi = movieApi; @@ -39,6 +41,7 @@ namespace Ombi.Core.Engine Logger = log; _requestLog = rl; _mediaCacheService = mediaCacheService; + _featureService = featureService; } private IMovieDbApi MovieApi { get; } @@ -47,6 +50,7 @@ namespace Ombi.Core.Engine private ILogger Logger { get; } private readonly IRepository _requestLog; private readonly IMediaCacheService _mediaCacheService; + private readonly IFeatureService _featureService; /// /// Requests the movie. @@ -72,7 +76,8 @@ namespace Ombi.Core.Engine var userDetails = await GetUser(); var canRequestOnBehalf = model.RequestOnBehalf.HasValue(); - var isAdmin = await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin); + var isAdmin = await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser) + || await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin); if (canRequestOnBehalf && !isAdmin) { return new RequestEngineResult @@ -93,27 +98,53 @@ namespace Ombi.Core.Engine }; } - var requestModel = new MovieRequests + var is4kFeatureEnabled = await _featureService.FeatureEnabled(FeatureNames.Movie4KRequests); + var is4kRequest = is4kFeatureEnabled && model.Is4kRequest; + + MovieRequests requestModel; + bool isExisting = false; + // Do we already have a request? 4k or non 4k + var existingRequest = await MovieRepository.GetRequestAsync(movieInfo.Id); + if (existingRequest != null && is4kFeatureEnabled) { - TheMovieDbId = movieInfo.Id, - RequestType = RequestType.Movie, - Overview = movieInfo.Overview, - ImdbId = movieInfo.ImdbId, - PosterPath = PosterPathHelper.FixPosterPath(movieInfo.PosterPath), - Title = movieInfo.Title, - ReleaseDate = !string.IsNullOrEmpty(movieInfo.ReleaseDate) - ? DateTime.Parse(movieInfo.ReleaseDate) - : DateTime.MinValue, - Status = movieInfo.Status, - RequestedDate = DateTime.UtcNow, - Approved = false, - RequestedUserId = canRequestOnBehalf ? model.RequestOnBehalf : userDetails.Id, - Background = movieInfo.BackdropPath, - LangCode = model.LanguageCode, - RequestedByAlias = model.RequestedByAlias, - RootPathOverride = model.RootFolderOverride.GetValueOrDefault(), - QualityOverride = model.QualityPathOverride.GetValueOrDefault() - }; + if (model.Is4kRequest) + { + existingRequest.Is4kRequest = true; + existingRequest.RequestedDate4k = DateTime.Now; + } + else + { + existingRequest.RequestedDate = DateTime.Now; + } + isExisting = true; + requestModel = existingRequest; + } + else + { + requestModel = new MovieRequests + { + TheMovieDbId = movieInfo.Id, + RequestType = RequestType.Movie, + Overview = movieInfo.Overview, + ImdbId = movieInfo.ImdbId, + PosterPath = PosterPathHelper.FixPosterPath(movieInfo.PosterPath), + Title = movieInfo.Title, + ReleaseDate = !string.IsNullOrEmpty(movieInfo.ReleaseDate) + ? DateTime.Parse(movieInfo.ReleaseDate) + : DateTime.MinValue, + Status = movieInfo.Status, + RequestedDate = model.Is4kRequest ? DateTime.MinValue : DateTime.Now, + Approved = false, + RequestedUserId = canRequestOnBehalf ? model.RequestOnBehalf : userDetails.Id, + Background = movieInfo.BackdropPath, + LangCode = model.LanguageCode, + RequestedByAlias = model.RequestedByAlias, + RootPathOverride = model.RootFolderOverride.GetValueOrDefault(), + QualityOverride = model.QualityPathOverride.GetValueOrDefault(), + RequestedDate4k = model.Is4kRequest ? DateTime.Now : DateTime.MinValue, + Is4kRequest = model.Is4kRequest + }; + } var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US"); requestModel.DigitalReleaseDate = usDates?.ReleaseDate @@ -132,10 +163,10 @@ namespace Ombi.Core.Engine if (requestModel.Approved) // The rules have auto approved this { - var requestEngineResult = await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf); + var requestEngineResult = await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf, isExisting, is4kRequest); if (requestEngineResult.Result) { - var result = await ApproveMovie(requestModel); + var result = await ApproveMovie(requestModel, model.Is4kRequest); if (result.IsError) { Logger.LogWarning("Tried auto sending movie but failed. Message: {0}", result.Message); @@ -153,7 +184,7 @@ namespace Ombi.Core.Engine // If there are no providers then it's successful but movie has not been sent } - return await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf); + return await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf, isExisting, is4kRequest); } @@ -508,13 +539,13 @@ namespace Ombi.Core.Engine return results; } - public async Task ApproveMovieById(int requestId) + public async Task ApproveMovieById(int requestId, bool is4K) { var request = await MovieRepository.Find(requestId); - return await ApproveMovie(request); + return await ApproveMovie(request, is4K); } - public async Task DenyMovieById(int modelId, string denyReason) + public async Task DenyMovieById(int modelId, string denyReason, bool is4K) { var request = await MovieRepository.Find(modelId); if (request == null) @@ -525,8 +556,16 @@ namespace Ombi.Core.Engine }; } - request.Denied = true; - request.DeniedReason = denyReason; + if (is4K) + { + request.Denied4K = true; + request.DeniedReason4K = denyReason; + } + else + { + request.Denied = true; + request.DeniedReason = denyReason; + } await MovieRepository.Update(request); await _mediaCacheService.Purge(); @@ -540,7 +579,7 @@ namespace Ombi.Core.Engine }; } - public async Task ApproveMovie(MovieRequests request) + public async Task ApproveMovie(MovieRequests request, bool is4K) { if (request == null) { @@ -550,9 +589,18 @@ namespace Ombi.Core.Engine }; } - request.MarkedAsApproved = DateTime.Now; - request.Approved = true; - request.Denied = false; + if (is4K) + { + request.MarkedAsApproved4K = DateTime.Now; + request.Approved4K = true; + request.Denied4K = false; + } + else + { + request.MarkedAsApproved = DateTime.Now; + request.Approved = true; + request.Denied = false; + } await MovieRepository.Update(request); var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification, string.Empty); @@ -562,7 +610,7 @@ namespace Ombi.Core.Engine } await _mediaCacheService.Purge(); - return await ProcessSendingMovie(request); + return await ProcessSendingMovie(request, is4K); } public async Task RequestCollection(int collectionId, CancellationToken cancellationToken) @@ -590,11 +638,11 @@ namespace Ombi.Core.Engine return new RequestEngineResult { Result = true, Message = $"The collection {collections.name} has been successfully added!", RequestId = results.FirstOrDefault().RequestId }; } - private async Task ProcessSendingMovie(MovieRequests request) + private async Task ProcessSendingMovie(MovieRequests request, bool is4K) { if (request.Approved) { - var result = await Sender.Send(request); + var result = await Sender.Send(request, is4K); if (result.Success && result.Sent) { return new RequestEngineResult @@ -662,7 +710,7 @@ namespace Ombi.Core.Engine var result = await CheckCanManageRequest(request); if (result.IsError) return result; - + await MovieRepository.Delete(request); await _mediaCacheService.Purge(); return new RequestEngineResult @@ -683,7 +731,7 @@ namespace Ombi.Core.Engine return await MovieRepository.GetAll().AnyAsync(x => x.RequestedUserId == userId); } - public async Task ReProcessRequest(int requestId, CancellationToken cancellationToken) + public async Task ReProcessRequest(int requestId, bool is4K, CancellationToken cancellationToken) { var request = await MovieRepository.Find(requestId); if (request == null) @@ -695,10 +743,10 @@ namespace Ombi.Core.Engine }; } - return await ProcessSendingMovie(request); + return await ProcessSendingMovie(request, is4K); } - public async Task MarkUnavailable(int modelId) + public async Task MarkUnavailable(int modelId, bool is4K) { var request = await MovieRepository.Find(modelId); if (request == null) @@ -709,7 +757,14 @@ namespace Ombi.Core.Engine }; } - request.Available = false; + if (is4K) + { + request.Available4K = false; + } + else + { + request.Available = false; + } await MovieRepository.Update(request); await _mediaCacheService.Purge(); @@ -720,7 +775,7 @@ namespace Ombi.Core.Engine }; } - public async Task MarkAvailable(int modelId) + public async Task MarkAvailable(int modelId, bool is4K) { var request = await MovieRepository.Find(modelId); if (request == null) @@ -730,9 +785,16 @@ namespace Ombi.Core.Engine ErrorMessage = "Request does not exist" }; } - - request.Available = true; - request.MarkedAsAvailable = DateTime.Now; + if (!is4K) + { + request.Available = true; + request.MarkedAsAvailable = DateTime.Now; + } + else + { + request.Available4K = true; + request.MarkedAsAvailable4K = DateTime.Now; + } await NotificationHelper.Notify(request, NotificationType.RequestAvailable); await MovieRepository.Update(request); await _mediaCacheService.Purge(); @@ -744,9 +806,20 @@ namespace Ombi.Core.Engine }; } - private async Task AddMovieRequest(MovieRequests model, string movieName, string requestOnBehalf) + private async Task AddMovieRequest(MovieRequests model, string movieName, string requestOnBehalf, bool isExisting, bool is4k) { - await MovieRepository.Add(model); + if (is4k) + { + model.Has4KRequest = true; + } + if (!isExisting) + { + await MovieRepository.Add(model); + } + else + { + await MovieRepository.Update(model); + } var result = await RunSpecificRule(model, SpecificRules.CanSendNotification, requestOnBehalf); if (result.Success) diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index edfa7b392..4b46e8151 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -793,7 +793,7 @@ namespace Ombi.Core.Engine return await TvRepository.GetChild().AnyAsync(x => x.RequestedUserId == userId); } - public async Task MarkUnavailable(int modelId) + public async Task MarkUnavailable(int modelId, bool is4K) { var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == modelId); if (request == null) @@ -821,7 +821,7 @@ namespace Ombi.Core.Engine }; } - public async Task MarkAvailable(int modelId) + public async Task MarkAvailable(int modelId, bool is4K) { ChildRequests request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == modelId); if (request == null) @@ -918,7 +918,7 @@ namespace Ombi.Core.Engine return await AfterRequest(model.ChildRequests.FirstOrDefault(), requestOnBehalf); } - public async Task ReProcessRequest(int requestId, CancellationToken cancellationToken) + public async Task ReProcessRequest(int requestId, bool is4K, CancellationToken cancellationToken) { var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId, cancellationToken); if (request == null) diff --git a/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs b/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs index 7c69e4d5d..d160435be 100644 --- a/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs +++ b/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs @@ -406,6 +406,13 @@ namespace Ombi.Core.Engine.V2 mapped.Subscribed = viewMovie.Subscribed; mapped.ShowSubscribe = viewMovie.ShowSubscribe; mapped.DigitalReleaseDate = viewMovie.DigitalReleaseDate; + mapped.RequestedDate4k = viewMovie.RequestedDate4k; + mapped.Approved4K = viewMovie.Approved4K; + mapped.Available4K = viewMovie.Available4K; + mapped.Denied4K = viewMovie.Denied4K; + mapped.DeniedReason4K = viewMovie.DeniedReason4K; + mapped.Has4KRequest = viewMovie.Has4KRequest; + return mapped; } diff --git a/src/Ombi.Core/Engine/VoteEngine.cs b/src/Ombi.Core/Engine/VoteEngine.cs index 5c73e03d9..a63ba1604 100644 --- a/src/Ombi.Core/Engine/VoteEngine.cs +++ b/src/Ombi.Core/Engine/VoteEngine.cs @@ -193,7 +193,7 @@ namespace Ombi.Core.Engine case RequestType.Movie: if (totalVotes >= voteSettings.MovieVoteMax) { - result = await _movieRequestEngine.ApproveMovieById(requestId); + result = await _movieRequestEngine.ApproveMovieById(requestId, false); } break; case RequestType.Album: diff --git a/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs b/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs index 05eec3e3f..39a5b6c2a 100644 --- a/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs +++ b/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs @@ -34,6 +34,8 @@ namespace Ombi.Core.Models.Requests public int TheMovieDbId { get; set; } public string LanguageCode { get; set; } = "en"; + public bool Is4kRequest { get; set; } + /// /// This is only set from a HTTP Header /// diff --git a/src/Ombi.Core/Models/Search/SearchMovieViewModel.cs b/src/Ombi.Core/Models/Search/SearchMovieViewModel.cs index 4d0d05a49..10cc76404 100644 --- a/src/Ombi.Core/Models/Search/SearchMovieViewModel.cs +++ b/src/Ombi.Core/Models/Search/SearchMovieViewModel.cs @@ -28,5 +28,14 @@ namespace Ombi.Core.Models.Search public override RequestType Type => RequestType.Movie; public ReleaseDatesDto ReleaseDates { get; set; } public DateTime? DigitalReleaseDate { get; set; } + public bool Has4KRequest { get; set; } + public bool Approved4K { get; set; } + public DateTime MarkedAsApproved4K { get; set; } + public DateTime RequestedDate4k { get; set; } + public bool Available4K { get; set; } + public DateTime? MarkedAsAvailable4K { get; set; } + public bool? Denied4K { get; set; } + public DateTime MarkedAsDenied4K { get; set; } + public string DeniedReason4K { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Models/Search/V2/MovieFullInfoViewModel.cs b/src/Ombi.Core/Models/Search/V2/MovieFullInfoViewModel.cs index 8e8dac9b1..847780e57 100644 --- a/src/Ombi.Core/Models/Search/V2/MovieFullInfoViewModel.cs +++ b/src/Ombi.Core/Models/Search/V2/MovieFullInfoViewModel.cs @@ -41,6 +41,15 @@ namespace Ombi.Core.Models.Search.V2 public Recommendations Recommendations { get; set; } public ExternalIds ExternalIds { get; set; } public Keywords Keywords { get; set; } + public bool Has4KRequest { get; set; } + public bool Approved4K { get; set; } + public DateTime MarkedAsApproved4K { get; set; } + public DateTime RequestedDate4k { get; set; } + public bool Available4K { get; set; } + public DateTime? MarkedAsAvailable4K { get; set; } + public bool? Denied4K { get; set; } + public DateTime MarkedAsDenied4K { get; set; } + public string DeniedReason4K { get; set; } } public class Keywords { diff --git a/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs b/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs index f427434f0..92c1b11a7 100644 --- a/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs @@ -5,7 +5,9 @@ using Microsoft.EntityFrameworkCore; using Ombi.Core.Authentication; using Ombi.Core.Models.Requests; using Ombi.Core.Rule.Interfaces; +using Ombi.Core.Services; using Ombi.Helpers; +using Ombi.Settings.Settings.Models; using Ombi.Store.Entities; using Ombi.Store.Entities.Requests; @@ -13,14 +15,16 @@ namespace Ombi.Core.Rule.Rules.Request { public class AutoApproveRule : BaseRequestRule, IRules { - public AutoApproveRule(IPrincipal principal, OmbiUserManager um) + public AutoApproveRule(IPrincipal principal, OmbiUserManager um, IFeatureService featureService) { User = principal; _manager = um; + _featureService = featureService; } private IPrincipal User { get; } private readonly OmbiUserManager _manager; + private readonly IFeatureService _featureService; public async Task Execute(BaseRequest obj) { @@ -28,17 +32,40 @@ namespace Ombi.Core.Rule.Rules.Request var user = await _manager.Users.FirstOrDefaultAsync(x => x.NormalizedUserName == username); if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin) || user.IsSystemUser) { - obj.Approved = true; + if (obj is MovieRequests movie) + { + await Check4K(movie); + } + else + { + obj.Approved = true; + } return Success(); } if (obj.RequestType == RequestType.Movie && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie)) - obj.Approved = true; + { + var movie = (MovieRequests)obj; + await Check4K(movie); + } if (obj.RequestType == RequestType.TvShow && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveTv)) obj.Approved = true; if (obj.RequestType == RequestType.Album && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMusic)) obj.Approved = true; return Success(); // We don't really care, we just don't set the obj to approve } + + private async Task Check4K(MovieRequests movie) + { + var featureEnabled = await _featureService.FeatureEnabled(FeatureNames.Movie4KRequests); + if (movie.Is4kRequest && featureEnabled) + { + movie.Approved4K = true; + } + else + { + movie.Approved = true; + } + } } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs b/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs index 1c720a385..2546e1f29 100644 --- a/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs @@ -33,8 +33,23 @@ namespace Ombi.Core.Rule.Rules.Request if (obj.RequestType == RequestType.Movie) { - if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMovie) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie)) - return Success(); + var movie = (MovieRequests)obj; + var hasAutoApprove = await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie); + if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMovie) || hasAutoApprove) + { + if (movie.Is4kRequest && !hasAutoApprove) + { + var has4kPermission = await _manager.IsInRoleAsync(user, OmbiRoles.Request4KMovie); + if (has4kPermission) + { + return Success(); + } + } + else + { + return Success(); + } + } return Fail(ErrorCode.NoPermissionsRequestMovie, "You do not have permissions to Request a Movie"); } diff --git a/src/Ombi.Core/Rule/Rules/Request/ExistingMovieRequestRule.cs b/src/Ombi.Core/Rule/Rules/Request/ExistingMovieRequestRule.cs index c837c42d1..17533a570 100644 --- a/src/Ombi.Core/Rule/Rules/Request/ExistingMovieRequestRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/ExistingMovieRequestRule.cs @@ -1,21 +1,24 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Ombi.Core.Rule.Interfaces; using Ombi.Helpers; using Ombi.Store.Entities; using Ombi.Store.Entities.Requests; -using Ombi.Store.Repository; using Ombi.Core.Engine; using Ombi.Store.Repository.Requests; +using Ombi.Core.Services; +using Ombi.Settings.Settings.Models; namespace Ombi.Core.Rule.Rules.Request { public class ExistingMovieRequestRule : BaseRequestRule, IRules { - public ExistingMovieRequestRule(IMovieRequestRepository movie) + private readonly IFeatureService _featureService; + + public ExistingMovieRequestRule(IMovieRequestRepository movie, IFeatureService featureService) { Movie = movie; + _featureService = featureService; } private IMovieRequestRepository Movie { get; } @@ -35,7 +38,7 @@ namespace Ombi.Core.Rule.Rules.Request var existing = await movieRequests.FirstOrDefaultAsync(x => x.TheMovieDbId == movie.TheMovieDbId); if (existing != null) // Do we already have a request for this? { - found = true; + found = await Check4KRequests(movie, existing); } if (!found && movie.ImdbId.HasValue()) @@ -45,15 +48,30 @@ namespace Ombi.Core.Rule.Rules.Request x.ImdbId == movie.ImdbId); if (existing != null) { - found = true; + found = await Check4KRequests(movie, existing); } } - if(found) + if (found) { return Fail(ErrorCode.AlreadyRequested, $"\"{obj.Title}\" has already been requested"); } } return Success(); } + + private async Task Check4KRequests(MovieRequests movie, MovieRequests existing) + { + var featureEnabled = await _featureService.FeatureEnabled(FeatureNames.Movie4KRequests); + if (movie.Is4kRequest && existing.Has4KRequest && featureEnabled) + { + return true; + } + if (!movie.Is4kRequest && !existing.Has4KRequest || !featureEnabled) + { + return true; + } + + return false; + } } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs index e7a629854..4bc42ad04 100644 --- a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs @@ -63,11 +63,29 @@ namespace Ombi.Core.Rule.Rules.Search } } } - + if (item != null) { - obj.Available = true; - obj.EmbyUrl = item.Url; + if (obj is SearchMovieViewModel movie) + { + if (item.Has4K) + { + movie.Available4K = true; + obj.EmbyUrl = item.Url; + } + + if (item.Quality.HasValue()) + { + obj.Available = true; + obj.EmbyUrl = item.Url; + obj.Quality = item.Quality; + } + } + else + { + obj.Available = true; + obj.EmbyUrl = item.Url; + } if (obj.Type == RequestType.TvShow) { diff --git a/src/Ombi.Core/Rule/Rules/Search/ExistingRule.cs b/src/Ombi.Core/Rule/Rules/Search/ExistingRule.cs index 58c2508a6..a9bf13265 100644 --- a/src/Ombi.Core/Rule/Rules/Search/ExistingRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/ExistingRule.cs @@ -26,17 +26,27 @@ namespace Ombi.Core.Rule.Rules.Search public async Task Execute(SearchViewModel obj) { - if (obj.Type == RequestType.Movie) + if (obj is SearchMovieViewModel movie) { var movieRequests = await Movie.GetRequestAsync(obj.Id); if (movieRequests != null) // Do we already have a request for this? { - obj.Requested = true; - obj.RequestId = movieRequests.Id; - obj.Approved = movieRequests.Approved; - obj.Denied = movieRequests.Denied ?? false; - obj.DeniedReason = movieRequests.DeniedReason; - obj.Available = movieRequests.Available; + // If the RequestDate is a min value, that means there's only a 4k request + movie.Requested = movieRequests.RequestedDate != DateTime.MinValue; + movie.RequestId = movieRequests.Id; + movie.Approved = movieRequests.Approved; + movie.Denied = movieRequests.Denied ?? false; + movie.DeniedReason = movieRequests.DeniedReason; + movie.Available = movieRequests.Available; + movie.Has4KRequest = movieRequests.Has4KRequest; + movie.RequestedDate4k = movieRequests.RequestedDate4k; + movie.Approved4K = movieRequests.Approved4K; + movie.Available4K = movieRequests.Available4K; + movie.Denied4K = movieRequests.Denied4K; + movie.DeniedReason4K = movieRequests.DeniedReason4K; + movie.MarkedAsApproved4K = movieRequests.MarkedAsApproved4K; + movie.MarkedAsAvailable4K = movieRequests.MarkedAsAvailable4K; + movie.MarkedAsDenied4K = movieRequests.MarkedAsDenied4K; return Success(); } diff --git a/src/Ombi.Core/Rule/Rules/Search/JellyfinAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/JellyfinAvailabilityRule.cs index 95a2da80b..f8c69c0f0 100644 --- a/src/Ombi.Core/Rule/Rules/Search/JellyfinAvailabilityRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/JellyfinAvailabilityRule.cs @@ -80,8 +80,26 @@ namespace Ombi.Core.Rule.Rules.Search obj.TheMovieDbId = obj.Id.ToString(); useTheMovieDb = true; } - obj.Available = true; - obj.JellyfinUrl = item.Url; + if (obj is SearchMovieViewModel movie) + { + if (item.Has4K) + { + movie.Available4K = true; + obj.JellyfinUrl = item.Url; + } + + if (item.Quality.HasValue()) + { + obj.Available = true; + obj.EmbyUrl = item.Url; + obj.Quality = item.Quality; + } + } + else + { + obj.Available = true; + obj.JellyfinUrl = item.Url; + } if (obj.Type == RequestType.TvShow) { diff --git a/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs index 4c05e0fe1..1742f9f20 100644 --- a/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs @@ -89,7 +89,25 @@ namespace Ombi.Core.Rule.Rules.Search obj.TheMovieDbId = obj.Id.ToString(); useTheMovieDb = true; } - obj.Available = true; + + if (obj is SearchMovieViewModel movie) + { + if (item.Has4K) + { + movie.Available4K = true; + } + + if (item.Quality.HasValue()) + { + obj.Available = true; + obj.Quality = item.Quality; + } + } + else + { + obj.Available = true; + } + if (item.Url.StartsWith("http")) { obj.PlexUrl = item.Url; @@ -99,11 +117,9 @@ namespace Ombi.Core.Rule.Rules.Search // legacy content obj.PlexUrl = PlexHelper.BuildPlexMediaUrl(item.Url, host); } - obj.Quality = item.Quality; - if (obj.Type == RequestType.TvShow) + if (obj is SearchTvShowViewModel search) { - var search = (SearchTvShowViewModel)obj; // Let's go through the episodes now if (search.SeasonRequests.Any()) { diff --git a/src/Ombi.Core/Rule/Rules/Search/RadarrCacheRule.cs b/src/Ombi.Core/Rule/Rules/Search/RadarrCacheRule.cs index 105681c82..63546f8c8 100644 --- a/src/Ombi.Core/Rule/Rules/Search/RadarrCacheRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/RadarrCacheRule.cs @@ -18,16 +18,23 @@ namespace Ombi.Core.Rule.Rules.Search public Task Execute(SearchViewModel obj) { - if (obj.Type == RequestType.Movie) + if (obj is SearchMovieViewModel movie) { // Check if it's in Radarr var result = _db.GetAll().FirstOrDefault(x => x.TheMovieDbId == obj.Id); if (result != null) { - obj.Approved = true; // It's in radarr so it's approved... Maybe have a new property called "Processing" or something? + movie.Approved = true; // It's in radarr so it's approved... Maybe have a new property called "Processing" or something? if (result.HasFile) { - obj.Available = true; + if (result.Has4K) + { + movie.Available4K = true; + } + if (result.HasRegular) + { + movie.Available = true; + } } } } diff --git a/src/Ombi.Core/Senders/IMovieSender.cs b/src/Ombi.Core/Senders/IMovieSender.cs index cf8ddf33b..dcf3f766f 100644 --- a/src/Ombi.Core/Senders/IMovieSender.cs +++ b/src/Ombi.Core/Senders/IMovieSender.cs @@ -6,6 +6,6 @@ namespace Ombi.Core { public interface IMovieSender { - Task Send(MovieRequests model); + Task Send(MovieRequests model, bool is4K); } } \ No newline at end of file diff --git a/src/Ombi.Core/Senders/MovieSender.cs b/src/Ombi.Core/Senders/MovieSender.cs index 87d686d35..36d40bdad 100644 --- a/src/Ombi.Core/Senders/MovieSender.cs +++ b/src/Ombi.Core/Senders/MovieSender.cs @@ -20,13 +20,12 @@ namespace Ombi.Core.Senders { public class MovieSender : IMovieSender { - public MovieSender(ISettingsService radarrSettings, IRadarrApi api, ILogger log, + public MovieSender(ISettingsService radarrSettings, ISettingsService radarr4kSettings, ILogger log, ISettingsService dogSettings, IDogNzbApi dogApi, ISettingsService cpSettings, ICouchPotatoApi cpApi, IRepository userProfiles, IRepository requestQueue, INotificationHelper notify, IRadarrV3Api radarrV3Api) { _radarrSettings = radarrSettings; - _radarrV2Api = api; _log = log; _dogNzbSettings = dogSettings; _dogNzbApi = dogApi; @@ -36,10 +35,11 @@ namespace Ombi.Core.Senders _requestQueuRepository = requestQueue; _notificationHelper = notify; _radarrV3Api = radarrV3Api; + _radarr4KSettings = radarr4kSettings; } private readonly ISettingsService _radarrSettings; - private readonly IRadarrApi _radarrV2Api; + private readonly ISettingsService _radarr4KSettings; private readonly ILogger _log; private readonly IDogNzbApi _dogNzbApi; private readonly ISettingsService _dogNzbSettings; @@ -50,16 +50,24 @@ namespace Ombi.Core.Senders private readonly INotificationHelper _notificationHelper; private readonly IRadarrV3Api _radarrV3Api; - public async Task Send(MovieRequests model) + public async Task Send(MovieRequests model, bool is4K) { try { var cpSettings = await _couchPotatoSettings.GetSettingsAsync(); - //var watcherSettings = await WatcherSettings.GetSettingsAsync(); - var radarrSettings = await _radarrSettings.GetSettingsAsync(); + + RadarrSettings radarrSettings; + if (is4K) + { + radarrSettings = await _radarr4KSettings.GetSettingsAsync(); + } + else + { + radarrSettings = await _radarrSettings.GetSettingsAsync(); + } if (radarrSettings.Enabled) { - return await SendToRadarr(model, radarrSettings); + return await SendToRadarr(model, is4K, radarrSettings); } var dogSettings = await _dogNzbSettings.GetSettingsAsync(); @@ -123,7 +131,7 @@ namespace Ombi.Core.Senders return await _dogNzbApi.AddMovie(settings.ApiKey, id); } - private async Task SendToRadarr(MovieRequests model, RadarrSettings settings) + private async Task SendToRadarr(MovieRequests model, bool is4K, RadarrSettings settings) { var qualityToUse = int.Parse(settings.DefaultQualityProfile); diff --git a/src/Ombi.Core/Services/FeatureService.cs b/src/Ombi.Core/Services/FeatureService.cs new file mode 100644 index 000000000..279c5c2c5 --- /dev/null +++ b/src/Ombi.Core/Services/FeatureService.cs @@ -0,0 +1,28 @@ +using Ombi.Core.Settings; +using Ombi.Settings.Settings.Models; +using System.Linq; +using System.Threading.Tasks; + +namespace Ombi.Core.Services +{ + public interface IFeatureService + { + Task FeatureEnabled(string featureName); + } + + public class FeatureService : IFeatureService + { + private readonly ISettingsService _featureSettings; + + public FeatureService(ISettingsService featureSettings) + { + _featureSettings = featureSettings; + } + + public async Task FeatureEnabled(string featureName) + { + var settings = await _featureSettings.GetSettingsAsync(); + return settings.Features?.Where(x => x.Name.Equals(featureName, System.StringComparison.InvariantCultureIgnoreCase)).Select(x => x.Enabled)?.FirstOrDefault() ?? false; + } + } +} diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index f81cd05da..3fb5cd643 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -224,6 +224,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddScoped(); } public static void RegisterJobs(this IServiceCollection services) diff --git a/src/Ombi.Helpers/OmbiRoles.cs b/src/Ombi.Helpers/OmbiRoles.cs index 02a480fdf..3174419c4 100644 --- a/src/Ombi.Helpers/OmbiRoles.cs +++ b/src/Ombi.Helpers/OmbiRoles.cs @@ -16,5 +16,6 @@ public const string ReceivesNewsletter = nameof(ReceivesNewsletter); public const string ManageOwnRequests = nameof(ManageOwnRequests); public const string EditCustomPage = nameof(EditCustomPage); + public const string Request4KMovie = nameof(Request4KMovie); } } \ No newline at end of file diff --git a/src/Ombi.Schedule.Tests/PlexContentSyncTests.cs b/src/Ombi.Schedule.Tests/PlexContentSyncTests.cs index 3b9fa33b0..da128794c 100644 --- a/src/Ombi.Schedule.Tests/PlexContentSyncTests.cs +++ b/src/Ombi.Schedule.Tests/PlexContentSyncTests.cs @@ -133,12 +133,88 @@ namespace Ombi.Schedule.Tests } })); - await _subject.MovieLoop(new PlexServers { Ip = "http://test.com/", Port = 80}, content, contentToAdd, contentProcessed); + await _subject.MovieLoop(new PlexServers { Ip = "http://test.com/", Port = 80 }, content, contentToAdd, contentProcessed); var first = contentToAdd.First(); Assert.That(first.ImdbId, Is.EqualTo("tt0322259")); _mocker.Verify(x => x.GetMetadata(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } + + [Test] + public async Task UpdatesExistingMovieWhen_WeFindAnotherQuality() + { + var content = new Mediacontainer + { + Metadata = new[] + { + new Metadata + { + ratingKey = 11, + title = "test1", + year = 2021, + type = "movie", + Media = new Medium[1] + { + new Medium + { + videoResolution = "4k" + } + } + }, + } + }; + var contentToAdd = new HashSet(); + var contentProcessed = new Dictionary(); + _mocker.Setup(x => + x.GetFirstContentByCustom(It.IsAny>>())) + .Returns(Task.FromResult(new PlexServerContent + { + Quality = "1080" + })); + + await _subject.MovieLoop(new PlexServers(), content, contentToAdd, contentProcessed); + + Assert.That(contentToAdd, Is.Empty); + _mocker.Verify(x => x.Update(It.Is(x => x.Quality == "1080" && x.Has4K)), Times.Once); + } + + [Test] + public async Task DoesNotUpdatesExistingMovieWhen_WeFindSameQuality() + { + var content = new Mediacontainer + { + Metadata = new[] + { + new Metadata + { + ratingKey = 11, + title = "test1", + year = 2021, + type = "movie", + Media = new Medium[1] + { + new Medium + { + videoResolution = "1080" + } + } + }, + } + }; + var contentToAdd = new HashSet(); + var contentProcessed = new Dictionary(); + _mocker.Setup(x => + x.GetFirstContentByCustom(It.IsAny>>())) + .Returns(Task.FromResult(new PlexServerContent + { + Quality = "1080" + })); + + await _subject.MovieLoop(new PlexServers(), content, contentToAdd, contentProcessed); + + Assert.That(contentToAdd, Is.Empty); + _mocker.Verify(x => x.Update(It.IsAny()), Times.Never); + } } } diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs index 852530be4..1c4416e36 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs @@ -53,10 +53,11 @@ namespace Ombi.Schedule.Jobs.Emby private async Task ProcessMovies() { - var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available); + var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available || (!x.Available4K && x.Has4KRequest)); foreach (var movie in movies) { + var has4kRequest = movie.Has4KRequest; EmbyContent embyContent = null; if (movie.TheMovieDbId > 0) { @@ -75,8 +76,18 @@ namespace Ombi.Schedule.Jobs.Emby _log.LogInformation("We have found the request {0} on Emby, sending the notification", movie?.Title ?? string.Empty); - movie.Available = true; - movie.MarkedAsAvailable = DateTime.Now; + if (has4kRequest && embyContent.Has4K) + { + movie.Available4K = true; + movie.MarkedAsAvailable4K = DateTime.Now; + } + + // If we have a non-4k versison then mark as available + if (embyContent.Quality.HasValue()) + { + movie.Available = true; + movie.MarkedAsAvailable = DateTime.Now; + } if (movie.Available) { var recipient = movie.RequestedUser.Email.HasValue() ? movie.RequestedUser.Email : string.Empty; diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs index 646cbdee0..89baa7e9d 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; @@ -86,10 +87,10 @@ namespace Ombi.Schedule.Jobs.Emby private async Task StartServerCache(EmbyServers server, bool recentlyAdded) { if (!ValidateSettings(server)) + { return; + } - //await _repo.ExecuteSql("DELETE FROM EmbyEpisode"); - //await _repo.ExecuteSql("DELETE FROM EmbyContent"); if (server.EmbySelectedLibraries.Any() && server.EmbySelectedLibraries.Any(x => x.Enabled)) { @@ -209,6 +210,7 @@ namespace Ombi.Schedule.Jobs.Emby var totalCount = movies.TotalRecordCount; var processed = 0; var mediaToAdd = new HashSet(); + var mediaToUpdate = new HashSet(); while (processed < totalCount) { foreach (var movie in movies.Items) @@ -219,13 +221,13 @@ namespace Ombi.Schedule.Jobs.Emby await Api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri); foreach (var item in movieInfo.Items) { - await ProcessMovies(item, mediaToAdd, server); + await ProcessMovies(item, mediaToAdd, mediaToUpdate, server); } } else { // Regular movie - await ProcessMovies(movie, mediaToAdd, server); + await ProcessMovies(movie, mediaToAdd, mediaToUpdate, server); } processed++; @@ -238,24 +240,32 @@ namespace Ombi.Schedule.Jobs.Emby movies = await Api.GetAllMovies(server.ApiKey, parentId, processed, AmountToTake, server.AdministratorId, server.FullUri); } await _repo.AddRange(mediaToAdd); + await _repo.UpdateRange(mediaToUpdate); mediaToAdd.Clear(); } } - private async Task ProcessMovies(EmbyMovie movieInfo, ICollection content, EmbyServers server) + private async Task ProcessMovies(EmbyMovie movieInfo, ICollection content, ICollection toUpdate, EmbyServers server) { + var quality = movieInfo.MediaStreams?.FirstOrDefault()?.DisplayTitle ?? string.Empty; + var has4K = false; + if (quality.Contains("4K", CompareOptions.IgnoreCase)) + { + has4K = true; + } + // Check if it exists var existingMovie = await _repo.GetByEmbyId(movieInfo.Id); var alreadyGoingToAdd = content.Any(x => x.EmbyId == movieInfo.Id); if (existingMovie == null && !alreadyGoingToAdd) { - if (!movieInfo.ProviderIds.Any()) { _logger.LogWarning($"Movie {movieInfo.Name} has no relevant metadata. Skipping."); return; } - _logger.LogDebug("Adding new movie {0}", movieInfo.Name); + _logger.LogDebug($"Adding new movie {movieInfo.Name}"); + content.Add(new EmbyContent { ImdbId = movieInfo.ProviderIds.Imdb, @@ -264,13 +274,26 @@ namespace Ombi.Schedule.Jobs.Emby Type = MediaType.Movie, EmbyId = movieInfo.Id, Url = EmbyHelper.GetEmbyMediaUrl(movieInfo.Id, server?.ServerId, server.ServerHostname), - AddedAt = DateTime.UtcNow + AddedAt = DateTime.UtcNow, + Quality = has4K ? null : quality, + Has4K = has4K }); } else { - // we have this - _logger.LogDebug("We already have movie {0}", movieInfo.Name); + if (!existingMovie.Quality.Equals(quality, StringComparison.InvariantCultureIgnoreCase)) + { + _logger.LogDebug($"We have found another quality for Movie '{movieInfo.Name}', Quality: '{quality}'"); + existingMovie.Quality = has4K ? null : quality; + existingMovie.Has4K = has4K; + + toUpdate.Add(existingMovie); + } + else + { + // we have this + _logger.LogDebug($"We already have movie {movieInfo.Name}"); + } } } diff --git a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs index 125b27fc3..dd183e537 100644 --- a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs @@ -80,10 +80,11 @@ namespace Ombi.Schedule.Jobs.Jellyfin private async Task ProcessMovies() { - var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available); + var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available || (!x.Available4K && x.Has4KRequest)); foreach (var movie in movies) { + var has4kRequest = movie.Has4KRequest; JellyfinContent jellyfinContent = null; if (movie.TheMovieDbId > 0) { @@ -102,8 +103,19 @@ namespace Ombi.Schedule.Jobs.Jellyfin _log.LogInformation("We have found the request {0} on Jellyfin, sending the notification", movie?.Title ?? string.Empty); - movie.Available = true; - movie.MarkedAsAvailable = DateTime.Now; + if (has4kRequest && jellyfinContent.Has4K) + { + movie.Available4K = true; + movie.MarkedAsAvailable4K = DateTime.Now; + } + + // If we have a non-4k versison then mark as available + if (jellyfinContent.Quality.HasValue()) + { + movie.Available = true; + movie.MarkedAsAvailable = DateTime.Now; + } + if (movie.Available) { var recipient = movie.RequestedUser.Email.HasValue() ? movie.RequestedUser.Email : string.Empty; diff --git a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs index fec5ca0ce..10a6ae98c 100644 --- a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; @@ -77,9 +78,6 @@ namespace Ombi.Schedule.Jobs.Jellyfin return; } - //await _repo.ExecuteSql("DELETE FROM JellyfinEpisode"); - //await _repo.ExecuteSql("DELETE FROM JellyfinContent"); - if (server.JellyfinSelectedLibraries.Any() && server.JellyfinSelectedLibraries.Any(x => x.Enabled)) { var movieLibsToFilter = server.JellyfinSelectedLibraries.Where(x => x.Enabled && x.CollectionType == "movies"); @@ -179,6 +177,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin var totalCount = movies.TotalRecordCount; var processed = 0; var mediaToAdd = new HashSet(); + var mediaToUpdate = new HashSet(); while (processed < totalCount) { foreach (var movie in movies.Items) @@ -189,7 +188,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin await Api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri); foreach (var item in movieInfo.Items) { - await ProcessMovies(item, mediaToAdd, server); + await ProcessMovies(item, mediaToAdd, mediaToUpdate, server); } processed++; @@ -198,20 +197,28 @@ namespace Ombi.Schedule.Jobs.Jellyfin { processed++; // Regular movie - await ProcessMovies(movie, mediaToAdd, server); + await ProcessMovies(movie, mediaToAdd, mediaToUpdate, server); } } // Get the next batch movies = await Api.GetAllMovies(server.ApiKey, parentId, processed, 200, server.AdministratorId, server.FullUri); await _repo.AddRange(mediaToAdd); + await _repo.UpdateRange(mediaToUpdate); mediaToAdd.Clear(); } } - private async Task ProcessMovies(JellyfinMovie movieInfo, ICollection content, JellyfinServers server) + private async Task ProcessMovies(JellyfinMovie movieInfo, ICollection content, ICollection toUpdate, JellyfinServers server) { + var quality = movieInfo.MediaStreams?.FirstOrDefault()?.DisplayTitle ?? string.Empty; + var has4K = false; + if (quality.Contains("4K", CompareOptions.IgnoreCase)) + { + has4K = true; + } + // Check if it exists var existingMovie = await _repo.GetByJellyfinId(movieInfo.Id); var alreadyGoingToAdd = content.Any(x => x.JellyfinId == movieInfo.Id); @@ -222,7 +229,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin _logger.LogWarning($"Movie {movieInfo.Name} has no relevant metadata. Skipping."); return; } - _logger.LogDebug("Adding new movie {0}", movieInfo.Name); + _logger.LogDebug($"Adding new movie {movieInfo.Name}"); content.Add(new JellyfinContent { ImdbId = movieInfo.ProviderIds.Imdb, @@ -231,13 +238,26 @@ namespace Ombi.Schedule.Jobs.Jellyfin Type = MediaType.Movie, JellyfinId = movieInfo.Id, Url = JellyfinHelper.GetJellyfinMediaUrl(movieInfo.Id, server?.ServerId, server.ServerHostname), - AddedAt = DateTime.UtcNow + AddedAt = DateTime.UtcNow, + Quality = has4K ? null : quality, + Has4K = has4K }); } else { - // we have this - _logger.LogDebug("We already have movie {0}", movieInfo.Name); + if (!existingMovie.Quality.Equals(quality, StringComparison.InvariantCultureIgnoreCase)) + { + _logger.LogDebug($"We have found another quality for Movie '{movieInfo.Name}', Quality: '{quality}'"); + existingMovie.Quality = has4K ? null : quality; + existingMovie.Has4K = has4K; + + toUpdate.Add(existingMovie); + } + else + { + // we have this + _logger.LogDebug($"We already have movie {movieInfo.Name}"); + } } } diff --git a/src/Ombi.Schedule/Jobs/Ombi/AutoDeleteRequests.cs b/src/Ombi.Schedule/Jobs/Ombi/AutoDeleteRequests.cs index a2a7e4ed1..eba3c4998 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/AutoDeleteRequests.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/AutoDeleteRequests.cs @@ -25,7 +25,7 @@ namespace Ombi.Schedule.Jobs.Ombi _ombiSettings = ombiSettings; _movieRequests = movieRequest; _tvRequestRepository = tvRequestRepository; - _musicRequestRepository = _musicRequestRepository; + _musicRequestRepository = musicRequestRepository; _logger = logger; } diff --git a/src/Ombi.Schedule/Jobs/Ombi/ResendFailedRequests.cs b/src/Ombi.Schedule/Jobs/Ombi/ResendFailedRequests.cs index 3a9a75835..e0df3752c 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/ResendFailedRequests.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/ResendFailedRequests.cs @@ -49,7 +49,9 @@ namespace Ombi.Schedule.Jobs.Ombi await _requestQueue.SaveChangesAsync(); continue; } - var result = await _movieSender.Send(movieRequest); + + // TODO probably need to add something to the request queue to better idenitfy if it's a 4k request + var result = await _movieSender.Send(movieRequest, movieRequest.Approved4K); if (result.Success) { request.Completed = DateTime.UtcNow; diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs b/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs index 74dc4aa96..a6e1b205b 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs @@ -180,16 +180,12 @@ namespace Ombi.Schedule.Jobs.Plex private async Task ProcessMovies() { // Get all non available - var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available); + var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available || (!x.Available4K && x.Has4KRequest)); var itemsForAvailbility = new List(); foreach (var movie in movies) { - if (movie.Available) - { - return; - } - + var has4kRequest = movie.Has4KRequest; PlexServerContent item = null; if (movie.ImdbId.HasValue()) { @@ -208,9 +204,23 @@ namespace Ombi.Schedule.Jobs.Plex continue; } - _log.LogInformation("[PAC] - Movie request {0} is now available, sending notification", $"{movie.Title} - {movie.Id}"); - movie.Available = true; - movie.MarkedAsAvailable = DateTime.UtcNow; + _log.LogInformation($"[PAC] - Movie request {movie.Title} - {movie.Id} is now available, sending notification"); + + if (has4kRequest && item.Has4K) + { + movie.Available4K = true; + movie.Approved4K = true; + movie.MarkedAsAvailable4K = DateTime.Now; + } + + // If we have a non-4k versison then mark as available + if (item.Quality.HasValue()) + { + movie.Available = true; + movie.Approved = true; + movie.MarkedAsAvailable = DateTime.Now; + } + itemsForAvailbility.Add(new AvailabilityModel { Id = movie.Id, @@ -222,9 +232,9 @@ namespace Ombi.Schedule.Jobs.Plex { await _movieRepo.SaveChangesAsync(); } - foreach (var i in itemsForAvailbility) - { + foreach (var i in itemsForAvailbility.DistinctBy(x => x.Id)) + { await _notificationService.Notify(new NotificationOptions { DateTime = DateTime.Now, @@ -234,8 +244,6 @@ namespace Ombi.Schedule.Jobs.Plex Recipient = i.RequestedUser }); } - - //await _repo.SaveChangesAsync(); } private bool _disposed; diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs index bdb7e70bc..04b28d6e7 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs @@ -293,7 +293,7 @@ namespace Ombi.Schedule.Jobs.Plex Dictionary contentProcessed) { Logger.LogDebug("Processing Movies"); - foreach (var movie in content?.Metadata ?? new Metadata[] { }) + foreach (var movie in content?.Metadata ?? Array.Empty()) { // Let's check if we have this movie @@ -302,11 +302,31 @@ namespace Ombi.Schedule.Jobs.Plex var existing = await Repo.GetFirstContentByCustom(x => x.Title == movie.title && x.ReleaseYear == movie.year.ToString() && x.Type == MediaType.Movie); - // The rating key keeps changing - //var existing = await Repo.GetByKey(movie.ratingKey); if (existing != null) { - Logger.LogDebug("We already have movie {0}", movie.title); + // We need to see if this is a different quality, + // We want to know if this is a 4k content for example + var foundQualities = movie.Media?.Select(x => x.videoResolution); + + foreach (var quality in foundQualities) + { + if (quality.Equals(existing.Quality)) + { + // We got it + continue; + } + + // We don't have this quality + if (quality.Equals("4k", StringComparison.InvariantCultureIgnoreCase)) + { + Logger.LogDebug($"We already have movie {movie.title}, But found a 4K version!"); + existing.Has4K = true; + await Repo.Update(existing); + } + } + + + Logger.LogDebug($"We already have movie {movie.title}"); continue; } @@ -349,6 +369,10 @@ namespace Ombi.Schedule.Jobs.Plex } var providerIds = PlexHelper.GetProviderIdsFromMetadata(guids.ToArray()); + var qualities = movie.Media?.Select(x => x.videoResolution); + var is4k = qualities != null && qualities.Any(x => x.Equals("4k", StringComparison.InvariantCultureIgnoreCase)); + var selectedQuality = is4k ? string.Empty : qualities?.OrderBy(x => x)?.FirstOrDefault() ?? string.Empty; + var item = new PlexServerContent { AddedAt = DateTime.Now, @@ -358,7 +382,8 @@ namespace Ombi.Schedule.Jobs.Plex Title = movie.title, Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, movie.ratingKey, servers.ServerHostname), Seasons = new List(), - Quality = movie.Media?.FirstOrDefault()?.videoResolution ?? string.Empty + Quality = selectedQuality, + Has4K = is4k, }; if (providerIds.ImdbId.HasValue()) { diff --git a/src/Ombi.Schedule/Jobs/Radarr/IRadarrSync.cs b/src/Ombi.Schedule/Jobs/Radarr/IRadarrSync.cs index 1fb191fd1..19178ca48 100644 --- a/src/Ombi.Schedule/Jobs/Radarr/IRadarrSync.cs +++ b/src/Ombi.Schedule/Jobs/Radarr/IRadarrSync.cs @@ -1,11 +1,6 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Ombi.Store.Entities; - -namespace Ombi.Schedule.Jobs.Radarr +namespace Ombi.Schedule.Jobs.Radarr { public interface IRadarrSync : IBaseJob { - Task> GetCachedContent(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs b/src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs index 3cda06281..eb97edeff 100644 --- a/src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs +++ b/src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -10,106 +9,114 @@ using Ombi.Helpers; using Ombi.Settings.Settings.Models.External; using Ombi.Store.Context; using Ombi.Store.Entities; +using Ombi.Store.Repository; using Quartz; -using Serilog; namespace Ombi.Schedule.Jobs.Radarr { public class RadarrSync : IRadarrSync { - public RadarrSync(ISettingsService radarr, IRadarrV3Api radarrApi, ILogger log, ExternalContext ctx) + public RadarrSync(ISettingsService radarr, ISettingsService radarr4k, IRadarrV3Api radarrApi, ILogger log, ExternalContext ctx, + IExternalRepository radarrRepo) { - RadarrSettings = radarr; - RadarrApi = radarrApi; - Logger = log; + _radarrSettings = radarr; + _radarr4kSettings = radarr4k; + _api = radarrApi; + _logger = log; _ctx = ctx; - RadarrSettings.ClearCache(); + _radarrRepo = radarrRepo; + _radarrSettings.ClearCache(); + _radarr4kSettings.ClearCache(); } - private ISettingsService RadarrSettings { get; } - private IRadarrV3Api RadarrApi { get; } - private ILogger Logger { get; } + private readonly ISettingsService _radarrSettings; + private readonly ISettingsService _radarr4kSettings; + private readonly IRadarrV3Api _api; + private readonly ILogger _logger; private readonly ExternalContext _ctx; - - private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1); + private readonly IExternalRepository _radarrRepo; public async Task Execute(IJobExecutionContext job) { - await SemaphoreSlim.WaitAsync(); try { - var settings = await RadarrSettings.GetSettingsAsync(); - if (settings.Enabled) + var strat = _ctx.Database.CreateExecutionStrategy(); + await strat.ExecuteAsync(async () => { - try + // Let's remove the old cached data + using var tran = await _ctx.Database.BeginTransactionAsync(); + await _ctx.Database.ExecuteSqlRawAsync("DELETE FROM RadarrCache"); + tran.Commit(); + }); + + var radarrSettings = _radarrSettings.GetSettingsAsync(); + var radarr4kSettings = _radarr4kSettings.GetSettingsAsync(); + await Process(await radarrSettings); + await Process(await radarr4kSettings); + } + catch (Exception) + { + _logger.LogInformation(LoggingEvents.RadarrCacher, "Radarr is not setup, cannot cache episodes"); + } + } + + private async Task Process(RadarrSettings settings) + { + if (settings.Enabled) + { + try + { + var movies = await _api.GetMovies(settings.ApiKey, settings.FullUri); + var existingMovies = _radarrRepo.GetAll(); + if (movies != null) { - var movies = await RadarrApi.GetMovies(settings.ApiKey, settings.FullUri); - if (movies != null) + var movieIds = new List(); + foreach (var m in movies) { - var strat = _ctx.Database.CreateExecutionStrategy(); - await strat.ExecuteAsync(async () => + if (m.monitored || m.hasFile) { - // Let's remove the old cached data - using (var tran = await _ctx.Database.BeginTransactionAsync()) + if (m.tmdbId > 0) { - await _ctx.Database.ExecuteSqlRawAsync("DELETE FROM RadarrCache"); - tran.Commit(); - } - }); + var is4k = m.movieFile?.quality?.quality?.resolution >= 2160; - var movieIds = new List(); - foreach (var m in movies) - { - if (m.monitored || m.hasFile) - { - if (m.tmdbId > 0) + // Do we have a cached movie for this already? + var existing = await existingMovies.FirstOrDefaultAsync(x => x.TheMovieDbId == m.tmdbId); + if (existing != null) + { + existing.Has4K = is4k; + existing.HasFile = m.hasFile; + } + else { movieIds.Add(new RadarrCache { TheMovieDbId = m.tmdbId, - HasFile = m.hasFile + HasFile = m.hasFile, + Has4K = is4k, + HasRegular = !is4k }); } - else - { - Logger.LogError("TMDBId is not > 0 for movie {0}", m.title); - } } - } - strat = _ctx.Database.CreateExecutionStrategy(); - await strat.ExecuteAsync(async () => - { - using (var tran = await _ctx.Database.BeginTransactionAsync()) + else { - await _ctx.RadarrCache.AddRangeAsync(movieIds); - - await _ctx.SaveChangesAsync(); - tran.Commit(); + _logger.LogError($"TMDBId is not > 0 for movie {m.title}"); } - }); + } } - await OmbiQuartz.TriggerJob(nameof(IArrAvailabilityChecker), "DVR"); - } - catch (System.Exception ex) - { - Logger.LogError(LoggingEvents.Cacher, ex, "Failed caching queued items from Radarr"); + // Save from the updates made to the existing movies (they are in the EF Change Tracker) + await _radarrRepo.SaveChangesAsync(); + + await _radarrRepo.AddRange(movieIds); } + + await OmbiQuartz.TriggerJob(nameof(IArrAvailabilityChecker), "DVR"); + } + catch (System.Exception ex) + { + _logger.LogError(LoggingEvents.Cacher, ex, "Failed caching queued items from Radarr"); } } - catch (Exception) - { - Logger.LogInformation(LoggingEvents.RadarrCacher, "Radarr is not setup, cannot cache episodes"); - } - finally - { - SemaphoreSlim.Release(); - } - } - - public async Task> GetCachedContent() - { - return await _ctx.RadarrCache.ToListAsync(); } private bool _disposed; diff --git a/src/Ombi.Settings/Settings/Models/External/RadarrSettings.cs b/src/Ombi.Settings/Settings/Models/External/RadarrSettings.cs index b074e28d6..1b3e0982f 100644 --- a/src/Ombi.Settings/Settings/Models/External/RadarrSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/RadarrSettings.cs @@ -10,4 +10,15 @@ public string MinimumAvailability { get; set; } public bool ScanForAvailability { get; set; } } + + public class Radarr4KSettings : RadarrSettings + { + // no additional properties needed + } + + public class RadarrCombinedModel + { + public RadarrSettings Radarr { get; set; } + public Radarr4KSettings Radarr4K { get; set; } + } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/FeatureSettings.cs b/src/Ombi.Settings/Settings/Models/FeatureSettings.cs new file mode 100644 index 000000000..6a285d422 --- /dev/null +++ b/src/Ombi.Settings/Settings/Models/FeatureSettings.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ombi.Settings.Settings.Models +{ + public class FeatureSettings : Settings + { + public List Features { get; set; } + } + + public class FeatureEnablement + { + public string Name { get; set; } + public bool Enabled { get; set; } + } + + public static class FeatureNames + { + public const string Movie4KRequests = nameof(Movie4KRequests); + } +} diff --git a/src/Ombi.Store/Context/SettingsContext.cs b/src/Ombi.Store/Context/SettingsContext.cs index 83ffa792d..39883cb92 100644 --- a/src/Ombi.Store/Context/SettingsContext.cs +++ b/src/Ombi.Store/Context/SettingsContext.cs @@ -1,5 +1,6 @@ using System.Linq; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; using Ombi.Store.Entities; namespace Ombi.Store.Context diff --git a/src/Ombi.Store/Entities/MediaServerContent.cs b/src/Ombi.Store/Entities/MediaServerContent.cs index df50efc86..4f086060b 100644 --- a/src/Ombi.Store/Entities/MediaServerContent.cs +++ b/src/Ombi.Store/Entities/MediaServerContent.cs @@ -12,7 +12,11 @@ namespace Ombi.Store.Entities public string TvDbId { get; set; } public string TheMovieDbId { get; set; } public MediaType Type { get; set; } - + /// + /// Only populated if it's not 4k + /// + public string Quality { get; set; } + public bool Has4K { get; set; } public string Url { get; set; } public ICollection Episodes { get; set; } diff --git a/src/Ombi.Store/Entities/PlexServerContent.cs b/src/Ombi.Store/Entities/PlexServerContent.cs index cba02c68c..f33ddb2f1 100644 --- a/src/Ombi.Store/Entities/PlexServerContent.cs +++ b/src/Ombi.Store/Entities/PlexServerContent.cs @@ -41,7 +41,6 @@ namespace Ombi.Store.Entities /// Plex's internal ID for this item /// public int Key { get; set; } - public string Quality { get; set; } public int? RequestId { get; set; } diff --git a/src/Ombi.Store/Entities/RadarrCache.cs b/src/Ombi.Store/Entities/RadarrCache.cs index 4b62412fc..553e84f2b 100644 --- a/src/Ombi.Store/Entities/RadarrCache.cs +++ b/src/Ombi.Store/Entities/RadarrCache.cs @@ -7,5 +7,7 @@ namespace Ombi.Store.Entities { public int TheMovieDbId { get; set; } public bool HasFile { get; set; } + public bool Has4K { get; set; } + public bool HasRegular { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Store/Entities/Requests/MovieRequests.cs b/src/Ombi.Store/Entities/Requests/MovieRequests.cs index 42b17be73..49189a387 100644 --- a/src/Ombi.Store/Entities/Requests/MovieRequests.cs +++ b/src/Ombi.Store/Entities/Requests/MovieRequests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using Newtonsoft.Json; +using System; namespace Ombi.Store.Entities.Requests { @@ -22,9 +23,28 @@ namespace Ombi.Store.Entities.Requests [NotMapped] public bool ShowSubscribe { get; set; } + /// + /// This is only used during the request process to identify if + /// it's a regular request or a 4k + /// + [NotMapped] + public bool Is4kRequest { get; set; } + public int RootPathOverride { get; set; } public int QualityOverride { get; set; } + public bool Has4KRequest { get; set; } + + public bool Approved4K { get; set; } + public DateTime MarkedAsApproved4K { get; set; } + public DateTime RequestedDate4k { get; set; } + public bool Available4K { get; set; } + public DateTime? MarkedAsAvailable4K { get; set; } + public bool? Denied4K { get; set; } + public DateTime MarkedAsDenied4K { get; set; } + public string DeniedReason4K { get; set; } + + /// /// Only Use for setting the Language Code, Use the LanguageCode property for reading /// diff --git a/src/Ombi.Store/MigrationHelper.cs b/src/Ombi.Store/MigrationHelper.cs new file mode 100644 index 000000000..58b6d6b68 --- /dev/null +++ b/src/Ombi.Store/MigrationHelper.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; + +namespace Ombi.Store +{ + internal static class MigrationHelper + { + public static void InsertRole(this MigrationBuilder mb, string role) + { + mb.Sql($@" +INSERT INTO AspnetRoles(Id, ConcurrencyStamp, Name, NormalizedName) +SELECT '{Guid.NewGuid()}','{Guid.NewGuid()}','{role}', '{role.ToUpper()}' +WHERE NOT EXISTS(SELECT 1 FROM AspnetRoles WHERE Name = '{role}');"); + } + + public static void InsertRoleMySql(this MigrationBuilder mb, string role) + { + mb.Sql($@" +INSERT INTO AspNetRoles(Id, ConcurrencyStamp, Name, NormalizedName) +SELECT '{Guid.NewGuid()}','{Guid.NewGuid()}','{role}', '{role.ToUpper()}' +WHERE NOT EXISTS(SELECT 1 FROM AspNetRoles WHERE Name = '{role}');"); + } + } +} diff --git a/src/Ombi.Store/Migrations/ExternalMySql/20220211213229_MediaServerQualities.Designer.cs b/src/Ombi.Store/Migrations/ExternalMySql/20220211213229_MediaServerQualities.Designer.cs new file mode 100644 index 000000000..ab88ff30d --- /dev/null +++ b/src/Ombi.Store/Migrations/ExternalMySql/20220211213229_MediaServerQualities.Designer.cs @@ -0,0 +1,527 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context.MySql; + +#nullable disable + +namespace Ombi.Store.Migrations.ExternalMySql +{ + [DbContext(typeof(ExternalMySqlContext))] + [Migration("20220211213229_MediaServerQualities")] + partial class MediaServerQualities + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("TheMovieDbId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("EmbyId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("longtext"); + + b.Property("Quality") + .HasColumnType("longtext"); + + b.Property("TheMovieDbId") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("TvDbId") + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("Url") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("EmbyId") + .HasColumnType("longtext"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("ParentId") + .HasColumnType("varchar(255)"); + + b.Property("ProviderId") + .HasColumnType("longtext"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.Property("TheMovieDbId") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("TvDbId") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("JellyfinId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("ProviderId") + .HasColumnType("longtext"); + + b.Property("Quality") + .HasColumnType("longtext"); + + b.Property("TheMovieDbId") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("TvDbId") + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("Url") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("JellyfinContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("JellyfinId") + .HasColumnType("longtext"); + + b.Property("ParentId") + .HasColumnType("varchar(255)"); + + b.Property("ProviderId") + .HasColumnType("longtext"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.Property("TheMovieDbId") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("TvDbId") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("JellyfinEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("ArtistId") + .HasColumnType("int"); + + b.Property("ForeignAlbumId") + .HasColumnType("longtext"); + + b.Property("Monitored") + .HasColumnType("tinyint(1)"); + + b.Property("PercentOfTracks") + .HasColumnType("decimal(65,30)"); + + b.Property("ReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("TrackCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ArtistId") + .HasColumnType("int"); + + b.Property("ArtistName") + .HasColumnType("longtext"); + + b.Property("ForeignArtistId") + .HasColumnType("longtext"); + + b.Property("Monitored") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("GrandparentKey") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("int"); + + b.Property("ParentKey") + .HasColumnType("int"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ParentKey") + .HasColumnType("int"); + + b.Property("PlexContentId") + .HasColumnType("int"); + + b.Property("PlexServerContentId") + .HasColumnType("int"); + + b.Property("SeasonKey") + .HasColumnType("int"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("int"); + + b.Property("Quality") + .HasColumnType("longtext"); + + b.Property("ReleaseYear") + .HasColumnType("longtext"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("TheMovieDbId") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("TvDbId") + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("Url") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Has4K") + .HasColumnType("tinyint(1)"); + + b.Property("HasFile") + .HasColumnType("tinyint(1)"); + + b.Property("HasRegular") + .HasColumnType("tinyint(1)"); + + b.Property("TheMovieDbId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("TvDbId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.Property("TvDbId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("TheMovieDbId") + .HasColumnType("int"); + + b.Property("TvDbId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("HasFile") + .HasColumnType("tinyint(1)"); + + b.Property("MovieDbId") + .HasColumnType("int"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.Property("TvDbId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b => + { + b.HasOne("Ombi.Store.Entities.JellyfinContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("JellyfinId"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", null) + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Navigation("Episodes"); + + b.Navigation("Seasons"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/ExternalMySql/20220211213229_MediaServerQualities.cs b/src/Ombi.Store/Migrations/ExternalMySql/20220211213229_MediaServerQualities.cs new file mode 100644 index 000000000..add7da05e --- /dev/null +++ b/src/Ombi.Store/Migrations/ExternalMySql/20220211213229_MediaServerQualities.cs @@ -0,0 +1,59 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Ombi.Store.Migrations.ExternalMySql +{ + public partial class MediaServerQualities : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Has4K", + table: "RadarrCache", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "HasRegular", + table: "RadarrCache", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "Quality", + table: "JellyfinContent", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "Quality", + table: "EmbyContent", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Has4K", + table: "RadarrCache"); + + migrationBuilder.DropColumn( + name: "HasRegular", + table: "RadarrCache"); + + migrationBuilder.DropColumn( + name: "Quality", + table: "JellyfinContent"); + + migrationBuilder.DropColumn( + name: "Quality", + table: "EmbyContent"); + } + } +} diff --git a/src/Ombi.Store/Migrations/ExternalMySql/20220212210902_MediaServer4k.Designer.cs b/src/Ombi.Store/Migrations/ExternalMySql/20220212210902_MediaServer4k.Designer.cs new file mode 100644 index 000000000..fbdc63e32 --- /dev/null +++ b/src/Ombi.Store/Migrations/ExternalMySql/20220212210902_MediaServer4k.Designer.cs @@ -0,0 +1,536 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context.MySql; + +#nullable disable + +namespace Ombi.Store.Migrations.ExternalMySql +{ + [DbContext(typeof(ExternalMySqlContext))] + [Migration("20220212210902_MediaServer4k")] + partial class MediaServer4k + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("TheMovieDbId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("EmbyId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("Has4K") + .HasColumnType("tinyint(1)"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("longtext"); + + b.Property("Quality") + .HasColumnType("longtext"); + + b.Property("TheMovieDbId") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("TvDbId") + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("Url") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("EmbyId") + .HasColumnType("longtext"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("ParentId") + .HasColumnType("varchar(255)"); + + b.Property("ProviderId") + .HasColumnType("longtext"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.Property("TheMovieDbId") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("TvDbId") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("Has4K") + .HasColumnType("tinyint(1)"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("JellyfinId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("ProviderId") + .HasColumnType("longtext"); + + b.Property("Quality") + .HasColumnType("longtext"); + + b.Property("TheMovieDbId") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("TvDbId") + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("Url") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("JellyfinContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("JellyfinId") + .HasColumnType("longtext"); + + b.Property("ParentId") + .HasColumnType("varchar(255)"); + + b.Property("ProviderId") + .HasColumnType("longtext"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.Property("TheMovieDbId") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("TvDbId") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("JellyfinEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("ArtistId") + .HasColumnType("int"); + + b.Property("ForeignAlbumId") + .HasColumnType("longtext"); + + b.Property("Monitored") + .HasColumnType("tinyint(1)"); + + b.Property("PercentOfTracks") + .HasColumnType("decimal(65,30)"); + + b.Property("ReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("TrackCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ArtistId") + .HasColumnType("int"); + + b.Property("ArtistName") + .HasColumnType("longtext"); + + b.Property("ForeignArtistId") + .HasColumnType("longtext"); + + b.Property("Monitored") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("GrandparentKey") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("int"); + + b.Property("ParentKey") + .HasColumnType("int"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ParentKey") + .HasColumnType("int"); + + b.Property("PlexContentId") + .HasColumnType("int"); + + b.Property("PlexServerContentId") + .HasColumnType("int"); + + b.Property("SeasonKey") + .HasColumnType("int"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("Has4K") + .HasColumnType("tinyint(1)"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("int"); + + b.Property("Quality") + .HasColumnType("longtext"); + + b.Property("ReleaseYear") + .HasColumnType("longtext"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("TheMovieDbId") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("TvDbId") + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("Url") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Has4K") + .HasColumnType("tinyint(1)"); + + b.Property("HasFile") + .HasColumnType("tinyint(1)"); + + b.Property("HasRegular") + .HasColumnType("tinyint(1)"); + + b.Property("TheMovieDbId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("TvDbId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.Property("TvDbId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("TheMovieDbId") + .HasColumnType("int"); + + b.Property("TvDbId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("HasFile") + .HasColumnType("tinyint(1)"); + + b.Property("MovieDbId") + .HasColumnType("int"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.Property("TvDbId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b => + { + b.HasOne("Ombi.Store.Entities.JellyfinContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("JellyfinId"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", null) + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Navigation("Episodes"); + + b.Navigation("Seasons"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/ExternalMySql/20220212210902_MediaServer4k.cs b/src/Ombi.Store/Migrations/ExternalMySql/20220212210902_MediaServer4k.cs new file mode 100644 index 000000000..c7e1888a6 --- /dev/null +++ b/src/Ombi.Store/Migrations/ExternalMySql/20220212210902_MediaServer4k.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Ombi.Store.Migrations.ExternalMySql +{ + public partial class MediaServer4k : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Has4K", + table: "PlexServerContent", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "Has4K", + table: "JellyfinContent", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "Has4K", + table: "EmbyContent", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Has4K", + table: "PlexServerContent"); + + migrationBuilder.DropColumn( + name: "Has4K", + table: "JellyfinContent"); + + migrationBuilder.DropColumn( + name: "Has4K", + table: "EmbyContent"); + } + } +} diff --git a/src/Ombi.Store/Migrations/ExternalMySql/ExternalMySqlContextModelSnapshot.cs b/src/Ombi.Store/Migrations/ExternalMySql/ExternalMySqlContextModelSnapshot.cs index dbdbe6f55..b75c45665 100644 --- a/src/Ombi.Store/Migrations/ExternalMySql/ExternalMySqlContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/ExternalMySql/ExternalMySqlContextModelSnapshot.cs @@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Ombi.Store.Context.MySql; +#nullable disable + namespace Ombi.Store.Migrations.ExternalMySql { [DbContext(typeof(ExternalMySqlContext))] @@ -14,8 +16,8 @@ namespace Ombi.Store.Migrations.ExternalMySql { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 64) - .HasAnnotation("ProductVersion", "5.0.1"); + .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => { @@ -44,12 +46,18 @@ namespace Ombi.Store.Migrations.ExternalMySql .IsRequired() .HasColumnType("varchar(255)"); + b.Property("Has4K") + .HasColumnType("tinyint(1)"); + b.Property("ImdbId") .HasColumnType("longtext"); b.Property("ProviderId") .HasColumnType("longtext"); + b.Property("Quality") + .HasColumnType("longtext"); + b.Property("TheMovieDbId") .HasColumnType("longtext"); @@ -122,6 +130,9 @@ namespace Ombi.Store.Migrations.ExternalMySql b.Property("AddedAt") .HasColumnType("datetime(6)"); + b.Property("Has4K") + .HasColumnType("tinyint(1)"); + b.Property("ImdbId") .HasColumnType("longtext"); @@ -132,6 +143,9 @@ namespace Ombi.Store.Migrations.ExternalMySql b.Property("ProviderId") .HasColumnType("longtext"); + b.Property("Quality") + .HasColumnType("longtext"); + b.Property("TheMovieDbId") .HasColumnType("longtext"); @@ -321,6 +335,9 @@ namespace Ombi.Store.Migrations.ExternalMySql b.Property("AddedAt") .HasColumnType("datetime(6)"); + b.Property("Has4K") + .HasColumnType("tinyint(1)"); + b.Property("ImdbId") .HasColumnType("longtext"); @@ -362,9 +379,15 @@ namespace Ombi.Store.Migrations.ExternalMySql .ValueGeneratedOnAdd() .HasColumnType("int"); + b.Property("Has4K") + .HasColumnType("tinyint(1)"); + b.Property("HasFile") .HasColumnType("tinyint(1)"); + b.Property("HasRegular") + .HasColumnType("tinyint(1)"); + b.Property("TheMovieDbId") .HasColumnType("int"); diff --git a/src/Ombi.Store/Migrations/ExternalSqlite/20220211213347_MediaServerQualities.Designer.cs b/src/Ombi.Store/Migrations/ExternalSqlite/20220211213347_MediaServerQualities.Designer.cs new file mode 100644 index 000000000..4ec8422d5 --- /dev/null +++ b/src/Ombi.Store/Migrations/ExternalSqlite/20220211213347_MediaServerQualities.Designer.cs @@ -0,0 +1,525 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context.Sqlite; + +#nullable disable + +namespace Ombi.Store.Migrations.ExternalSqlite +{ + [DbContext(typeof(ExternalSqliteContext))] + [Migration("20220211213347_MediaServerQualities")] + partial class MediaServerQualities + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.0"); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("TheMovieDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("EmbyId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Quality") + .HasColumnType("TEXT"); + + b.Property("TheMovieDbId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TvDbId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("Url") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("EmbyId") + .HasColumnType("TEXT"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("ParentId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("TheMovieDbId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TvDbId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("JellyfinId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Quality") + .HasColumnType("TEXT"); + + b.Property("TheMovieDbId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TvDbId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("Url") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("JellyfinContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("JellyfinId") + .HasColumnType("TEXT"); + + b.Property("ParentId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("TheMovieDbId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TvDbId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("JellyfinEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("ArtistId") + .HasColumnType("INTEGER"); + + b.Property("ForeignAlbumId") + .HasColumnType("TEXT"); + + b.Property("Monitored") + .HasColumnType("INTEGER"); + + b.Property("PercentOfTracks") + .HasColumnType("TEXT"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TrackCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArtistId") + .HasColumnType("INTEGER"); + + b.Property("ArtistName") + .HasColumnType("TEXT"); + + b.Property("ForeignArtistId") + .HasColumnType("TEXT"); + + b.Property("Monitored") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("GrandparentKey") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("INTEGER"); + + b.Property("ParentKey") + .HasColumnType("INTEGER"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ParentKey") + .HasColumnType("INTEGER"); + + b.Property("PlexContentId") + .HasColumnType("INTEGER"); + + b.Property("PlexServerContentId") + .HasColumnType("INTEGER"); + + b.Property("SeasonKey") + .HasColumnType("INTEGER"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("INTEGER"); + + b.Property("Quality") + .HasColumnType("TEXT"); + + b.Property("ReleaseYear") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("TheMovieDbId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TvDbId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("Url") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Has4K") + .HasColumnType("INTEGER"); + + b.Property("HasFile") + .HasColumnType("INTEGER"); + + b.Property("HasRegular") + .HasColumnType("INTEGER"); + + b.Property("TheMovieDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("TvDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("TvDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("TheMovieDbId") + .HasColumnType("INTEGER"); + + b.Property("TvDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("HasFile") + .HasColumnType("INTEGER"); + + b.Property("MovieDbId") + .HasColumnType("INTEGER"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("TvDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b => + { + b.HasOne("Ombi.Store.Entities.JellyfinContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("JellyfinId"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", null) + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Navigation("Episodes"); + + b.Navigation("Seasons"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/ExternalSqlite/20220211213347_MediaServerQualities.cs b/src/Ombi.Store/Migrations/ExternalSqlite/20220211213347_MediaServerQualities.cs new file mode 100644 index 000000000..b194b7845 --- /dev/null +++ b/src/Ombi.Store/Migrations/ExternalSqlite/20220211213347_MediaServerQualities.cs @@ -0,0 +1,57 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Ombi.Store.Migrations.ExternalSqlite +{ + public partial class MediaServerQualities : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Has4K", + table: "RadarrCache", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "HasRegular", + table: "RadarrCache", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "Quality", + table: "JellyfinContent", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "Quality", + table: "EmbyContent", + type: "TEXT", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Has4K", + table: "RadarrCache"); + + migrationBuilder.DropColumn( + name: "HasRegular", + table: "RadarrCache"); + + migrationBuilder.DropColumn( + name: "Quality", + table: "JellyfinContent"); + + migrationBuilder.DropColumn( + name: "Quality", + table: "EmbyContent"); + } + } +} diff --git a/src/Ombi.Store/Migrations/ExternalSqlite/20220212210807_MediaServer4k.Designer.cs b/src/Ombi.Store/Migrations/ExternalSqlite/20220212210807_MediaServer4k.Designer.cs new file mode 100644 index 000000000..70cd280e9 --- /dev/null +++ b/src/Ombi.Store/Migrations/ExternalSqlite/20220212210807_MediaServer4k.Designer.cs @@ -0,0 +1,534 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context.Sqlite; + +#nullable disable + +namespace Ombi.Store.Migrations.ExternalSqlite +{ + [DbContext(typeof(ExternalSqliteContext))] + [Migration("20220212210807_MediaServer4k")] + partial class MediaServer4k + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.0"); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("TheMovieDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("EmbyId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Has4K") + .HasColumnType("INTEGER"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Quality") + .HasColumnType("TEXT"); + + b.Property("TheMovieDbId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TvDbId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("Url") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("EmbyId") + .HasColumnType("TEXT"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("ParentId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("TheMovieDbId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TvDbId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("Has4K") + .HasColumnType("INTEGER"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("JellyfinId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Quality") + .HasColumnType("TEXT"); + + b.Property("TheMovieDbId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TvDbId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("Url") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("JellyfinContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("JellyfinId") + .HasColumnType("TEXT"); + + b.Property("ParentId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("TheMovieDbId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TvDbId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("JellyfinEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("ArtistId") + .HasColumnType("INTEGER"); + + b.Property("ForeignAlbumId") + .HasColumnType("TEXT"); + + b.Property("Monitored") + .HasColumnType("INTEGER"); + + b.Property("PercentOfTracks") + .HasColumnType("TEXT"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TrackCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ArtistId") + .HasColumnType("INTEGER"); + + b.Property("ArtistName") + .HasColumnType("TEXT"); + + b.Property("ForeignArtistId") + .HasColumnType("TEXT"); + + b.Property("Monitored") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("GrandparentKey") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("INTEGER"); + + b.Property("ParentKey") + .HasColumnType("INTEGER"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ParentKey") + .HasColumnType("INTEGER"); + + b.Property("PlexContentId") + .HasColumnType("INTEGER"); + + b.Property("PlexServerContentId") + .HasColumnType("INTEGER"); + + b.Property("SeasonKey") + .HasColumnType("INTEGER"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("Has4K") + .HasColumnType("INTEGER"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("INTEGER"); + + b.Property("Quality") + .HasColumnType("TEXT"); + + b.Property("ReleaseYear") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("TheMovieDbId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TvDbId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("Url") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Has4K") + .HasColumnType("INTEGER"); + + b.Property("HasFile") + .HasColumnType("INTEGER"); + + b.Property("HasRegular") + .HasColumnType("INTEGER"); + + b.Property("TheMovieDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("TvDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("TvDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("TheMovieDbId") + .HasColumnType("INTEGER"); + + b.Property("TvDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("HasFile") + .HasColumnType("INTEGER"); + + b.Property("MovieDbId") + .HasColumnType("INTEGER"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("TvDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b => + { + b.HasOne("Ombi.Store.Entities.JellyfinContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("JellyfinId"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", null) + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Navigation("Episodes"); + + b.Navigation("Seasons"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/ExternalSqlite/20220212210807_MediaServer4k.cs b/src/Ombi.Store/Migrations/ExternalSqlite/20220212210807_MediaServer4k.cs new file mode 100644 index 000000000..593465c0a --- /dev/null +++ b/src/Ombi.Store/Migrations/ExternalSqlite/20220212210807_MediaServer4k.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Ombi.Store.Migrations.ExternalSqlite +{ + public partial class MediaServer4k : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Has4K", + table: "PlexServerContent", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "Has4K", + table: "JellyfinContent", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "Has4K", + table: "EmbyContent", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Has4K", + table: "PlexServerContent"); + + migrationBuilder.DropColumn( + name: "Has4K", + table: "JellyfinContent"); + + migrationBuilder.DropColumn( + name: "Has4K", + table: "EmbyContent"); + } + } +} diff --git a/src/Ombi.Store/Migrations/ExternalSqlite/ExternalSqliteContextModelSnapshot.cs b/src/Ombi.Store/Migrations/ExternalSqlite/ExternalSqliteContextModelSnapshot.cs index 00d4f44f4..302f5f537 100644 --- a/src/Ombi.Store/Migrations/ExternalSqlite/ExternalSqliteContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/ExternalSqlite/ExternalSqliteContextModelSnapshot.cs @@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Ombi.Store.Context.Sqlite; +#nullable disable + namespace Ombi.Store.Migrations.ExternalSqlite { [DbContext(typeof(ExternalSqliteContext))] @@ -13,8 +15,7 @@ namespace Ombi.Store.Migrations.ExternalSqlite protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.1"); + modelBuilder.HasAnnotation("ProductVersion", "6.0.0"); modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => { @@ -43,12 +44,18 @@ namespace Ombi.Store.Migrations.ExternalSqlite .IsRequired() .HasColumnType("TEXT"); + b.Property("Has4K") + .HasColumnType("INTEGER"); + b.Property("ImdbId") .HasColumnType("TEXT"); b.Property("ProviderId") .HasColumnType("TEXT"); + b.Property("Quality") + .HasColumnType("TEXT"); + b.Property("TheMovieDbId") .HasColumnType("TEXT"); @@ -121,6 +128,9 @@ namespace Ombi.Store.Migrations.ExternalSqlite b.Property("AddedAt") .HasColumnType("TEXT"); + b.Property("Has4K") + .HasColumnType("INTEGER"); + b.Property("ImdbId") .HasColumnType("TEXT"); @@ -131,6 +141,9 @@ namespace Ombi.Store.Migrations.ExternalSqlite b.Property("ProviderId") .HasColumnType("TEXT"); + b.Property("Quality") + .HasColumnType("TEXT"); + b.Property("TheMovieDbId") .HasColumnType("TEXT"); @@ -320,6 +333,9 @@ namespace Ombi.Store.Migrations.ExternalSqlite b.Property("AddedAt") .HasColumnType("TEXT"); + b.Property("Has4K") + .HasColumnType("INTEGER"); + b.Property("ImdbId") .HasColumnType("TEXT"); @@ -361,9 +377,15 @@ namespace Ombi.Store.Migrations.ExternalSqlite .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("Has4K") + .HasColumnType("INTEGER"); + b.Property("HasFile") .HasColumnType("INTEGER"); + b.Property("HasRegular") + .HasColumnType("INTEGER"); + b.Property("TheMovieDbId") .HasColumnType("INTEGER"); diff --git a/src/Ombi.Store/Migrations/OmbiMySql/20220210195008_Radarr4kRole.Designer.cs b/src/Ombi.Store/Migrations/OmbiMySql/20220210195008_Radarr4kRole.Designer.cs new file mode 100644 index 000000000..713672bcf --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiMySql/20220210195008_Radarr4kRole.Designer.cs @@ -0,0 +1,1246 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context.MySql; + +#nullable disable + +namespace Ombi.Store.Migrations.OmbiMySql +{ + [DbContext(typeof(OmbiMySqlContext))] + [Migration("20220210195008_Radarr4kRole")] + partial class Radarr4kRole + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("ProviderKey") + .HasColumnType("varchar(255)"); + + b.Property("ProviderDisplayName") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("RoleId") + .HasColumnType("varchar(255)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .HasColumnType("varchar(255)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AuditArea") + .HasColumnType("int"); + + b.Property("AuditType") + .HasColumnType("int"); + + b.Property("DateTime") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("User") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("Token") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("MobileDevices"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Agent") + .HasColumnType("int"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Message") + .HasColumnType("longtext"); + + b.Property("NotificationType") + .HasColumnType("int"); + + b.Property("Subject") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("PlayerId") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("Alias") + .HasColumnType("longtext"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("EpisodeRequestLimit") + .HasColumnType("int"); + + b.Property("EpisodeRequestLimitType") + .HasColumnType("int"); + + b.Property("Language") + .HasColumnType("longtext"); + + b.Property("LastLoggedIn") + .HasColumnType("datetime(6)"); + + b.Property("LockoutEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnd") + .HasColumnType("datetime(6)"); + + b.Property("MovieRequestLimit") + .HasColumnType("int"); + + b.Property("MovieRequestLimitType") + .HasColumnType("int"); + + b.Property("MusicRequestLimit") + .HasColumnType("int"); + + b.Property("MusicRequestLimitType") + .HasColumnType("int"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + b.Property("PhoneNumber") + .HasColumnType("longtext"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("ProviderUserId") + .HasColumnType("longtext"); + + b.Property("SecurityStamp") + .HasColumnType("longtext"); + + b.Property("StreamingCountry") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TwoFactorEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UserAccessToken") + .HasColumnType("longtext"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("AlbumId") + .HasColumnType("longtext"); + + b.Property("ContentId") + .HasColumnType("int"); + + b.Property("ContentType") + .HasColumnType("int"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Completed") + .HasColumnType("datetime(6)"); + + b.Property("Dts") + .HasColumnType("datetime(6)"); + + b.Property("Error") + .HasColumnType("longtext"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RetryCount") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("RequestQueue"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("ArtistName") + .HasColumnType("longtext"); + + b.Property("Available") + .HasColumnType("tinyint(1)"); + + b.Property("Cover") + .HasColumnType("longtext"); + + b.Property("Denied") + .HasColumnType("tinyint(1)"); + + b.Property("DeniedReason") + .HasColumnType("longtext"); + + b.Property("Disk") + .HasColumnType("longtext"); + + b.Property("ForeignAlbumId") + .HasColumnType("longtext"); + + b.Property("ForeignArtistId") + .HasColumnType("longtext"); + + b.Property("MarkedAsApproved") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsAvailable") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsDenied") + .HasColumnType("datetime(6)"); + + b.Property("Rating") + .HasColumnType("decimal(65,30)"); + + b.Property("ReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("RequestedByAlias") + .HasColumnType("longtext"); + + b.Property("RequestedDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestedUserId") + .HasColumnType("varchar(255)"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("Available") + .HasColumnType("tinyint(1)"); + + b.Property("Denied") + .HasColumnType("tinyint(1)"); + + b.Property("DeniedReason") + .HasColumnType("longtext"); + + b.Property("IssueId") + .HasColumnType("int"); + + b.Property("MarkedAsApproved") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsAvailable") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsDenied") + .HasColumnType("datetime(6)"); + + b.Property("ParentRequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("RequestedByAlias") + .HasColumnType("longtext"); + + b.Property("RequestedDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestedUserId") + .HasColumnType("varchar(255)"); + + b.Property("SeriesType") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Comment") + .HasColumnType("longtext"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("IssuesId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("IssueCategoryId") + .HasColumnType("int"); + + b.Property("IssueId") + .HasColumnType("int"); + + b.Property("ProviderId") + .HasColumnType("longtext"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("ResovledDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Subject") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("UserReportedId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("Available") + .HasColumnType("tinyint(1)"); + + b.Property("Background") + .HasColumnType("longtext"); + + b.Property("Denied") + .HasColumnType("tinyint(1)"); + + b.Property("DeniedReason") + .HasColumnType("longtext"); + + b.Property("DigitalReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("IssueId") + .HasColumnType("int"); + + b.Property("LangCode") + .HasColumnType("longtext"); + + b.Property("MarkedAsApproved") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsAvailable") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsDenied") + .HasColumnType("datetime(6)"); + + b.Property("Overview") + .HasColumnType("longtext"); + + b.Property("PosterPath") + .HasColumnType("longtext"); + + b.Property("QualityOverride") + .HasColumnType("int"); + + b.Property("ReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("RequestedByAlias") + .HasColumnType("longtext"); + + b.Property("RequestedDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestedUserId") + .HasColumnType("varchar(255)"); + + b.Property("RootPathOverride") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("longtext"); + + b.Property("TheMovieDbId") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("EpisodeCount") + .HasColumnType("int"); + + b.Property("RequestDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Background") + .HasColumnType("longtext"); + + b.Property("ExternalProviderId") + .HasColumnType("int"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("LanguageProfile") + .HasColumnType("int"); + + b.Property("Overview") + .HasColumnType("longtext"); + + b.Property("PosterPath") + .HasColumnType("longtext"); + + b.Property("QualityOverride") + .HasColumnType("int"); + + b.Property("ReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("RootFolder") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("TotalSeasons") + .HasColumnType("int"); + + b.Property("TvDbId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Token") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Agent") + .HasColumnType("int"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("RadarrQualityProfile") + .HasColumnType("int"); + + b.Property("RadarrRootPath") + .HasColumnType("int"); + + b.Property("SonarrQualityProfile") + .HasColumnType("int"); + + b.Property("SonarrQualityProfileAnime") + .HasColumnType("int"); + + b.Property("SonarrRootPath") + .HasColumnType("int"); + + b.Property("SonarrRootPathAnime") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Deleted") + .HasColumnType("tinyint(1)"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("VoteType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AirDate") + .HasColumnType("datetime(6)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("Available") + .HasColumnType("tinyint(1)"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("Requested") + .HasColumnType("tinyint(1)"); + + b.Property("SeasonId") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("Url") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ChildRequestId") + .HasColumnType("int"); + + b.Property("Overview") + .HasColumnType("longtext"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("ParentRequest"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Issues"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + + b.Navigation("IssueCategory"); + + b.Navigation("UserReported"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRequest"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Navigation("NotificationUserIds"); + + b.Navigation("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Navigation("Issues"); + + b.Navigation("SeasonRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Navigation("Comments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Navigation("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Navigation("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Navigation("Episodes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiMySql/20220210195008_Radarr4kRole.cs b/src/Ombi.Store/Migrations/OmbiMySql/20220210195008_Radarr4kRole.cs new file mode 100644 index 000000000..88eeed9f2 --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiMySql/20220210195008_Radarr4kRole.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Ombi.Helpers; + +#nullable disable + +namespace Ombi.Store.Migrations.OmbiMySql +{ + public partial class Radarr4kRole : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.InsertRoleMySql(OmbiRoles.Request4KMovie); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiMySql/20220210201011_MovieRequest4K.Designer.cs b/src/Ombi.Store/Migrations/OmbiMySql/20220210201011_MovieRequest4K.Designer.cs new file mode 100644 index 000000000..7a6c430bd --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiMySql/20220210201011_MovieRequest4K.Designer.cs @@ -0,0 +1,1249 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context.MySql; + +#nullable disable + +namespace Ombi.Store.Migrations.OmbiMySql +{ + [DbContext(typeof(OmbiMySqlContext))] + [Migration("20220210201011_MovieRequest4K")] + partial class MovieRequest4K + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("ProviderKey") + .HasColumnType("varchar(255)"); + + b.Property("ProviderDisplayName") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("RoleId") + .HasColumnType("varchar(255)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .HasColumnType("varchar(255)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AuditArea") + .HasColumnType("int"); + + b.Property("AuditType") + .HasColumnType("int"); + + b.Property("DateTime") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("User") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("Token") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("MobileDevices"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Agent") + .HasColumnType("int"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Message") + .HasColumnType("longtext"); + + b.Property("NotificationType") + .HasColumnType("int"); + + b.Property("Subject") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("PlayerId") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("Alias") + .HasColumnType("longtext"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("EpisodeRequestLimit") + .HasColumnType("int"); + + b.Property("EpisodeRequestLimitType") + .HasColumnType("int"); + + b.Property("Language") + .HasColumnType("longtext"); + + b.Property("LastLoggedIn") + .HasColumnType("datetime(6)"); + + b.Property("LockoutEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnd") + .HasColumnType("datetime(6)"); + + b.Property("MovieRequestLimit") + .HasColumnType("int"); + + b.Property("MovieRequestLimitType") + .HasColumnType("int"); + + b.Property("MusicRequestLimit") + .HasColumnType("int"); + + b.Property("MusicRequestLimitType") + .HasColumnType("int"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + b.Property("PhoneNumber") + .HasColumnType("longtext"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("ProviderUserId") + .HasColumnType("longtext"); + + b.Property("SecurityStamp") + .HasColumnType("longtext"); + + b.Property("StreamingCountry") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TwoFactorEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UserAccessToken") + .HasColumnType("longtext"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("AlbumId") + .HasColumnType("longtext"); + + b.Property("ContentId") + .HasColumnType("int"); + + b.Property("ContentType") + .HasColumnType("int"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Completed") + .HasColumnType("datetime(6)"); + + b.Property("Dts") + .HasColumnType("datetime(6)"); + + b.Property("Error") + .HasColumnType("longtext"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RetryCount") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("RequestQueue"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("ArtistName") + .HasColumnType("longtext"); + + b.Property("Available") + .HasColumnType("tinyint(1)"); + + b.Property("Cover") + .HasColumnType("longtext"); + + b.Property("Denied") + .HasColumnType("tinyint(1)"); + + b.Property("DeniedReason") + .HasColumnType("longtext"); + + b.Property("Disk") + .HasColumnType("longtext"); + + b.Property("ForeignAlbumId") + .HasColumnType("longtext"); + + b.Property("ForeignArtistId") + .HasColumnType("longtext"); + + b.Property("MarkedAsApproved") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsAvailable") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsDenied") + .HasColumnType("datetime(6)"); + + b.Property("Rating") + .HasColumnType("decimal(65,30)"); + + b.Property("ReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("RequestedByAlias") + .HasColumnType("longtext"); + + b.Property("RequestedDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestedUserId") + .HasColumnType("varchar(255)"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("Available") + .HasColumnType("tinyint(1)"); + + b.Property("Denied") + .HasColumnType("tinyint(1)"); + + b.Property("DeniedReason") + .HasColumnType("longtext"); + + b.Property("IssueId") + .HasColumnType("int"); + + b.Property("MarkedAsApproved") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsAvailable") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsDenied") + .HasColumnType("datetime(6)"); + + b.Property("ParentRequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("RequestedByAlias") + .HasColumnType("longtext"); + + b.Property("RequestedDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestedUserId") + .HasColumnType("varchar(255)"); + + b.Property("SeriesType") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Comment") + .HasColumnType("longtext"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("IssuesId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("IssueCategoryId") + .HasColumnType("int"); + + b.Property("IssueId") + .HasColumnType("int"); + + b.Property("ProviderId") + .HasColumnType("longtext"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("ResovledDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Subject") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("UserReportedId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("Available") + .HasColumnType("tinyint(1)"); + + b.Property("Background") + .HasColumnType("longtext"); + + b.Property("Denied") + .HasColumnType("tinyint(1)"); + + b.Property("DeniedReason") + .HasColumnType("longtext"); + + b.Property("DigitalReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("Is4KRequest") + .HasColumnType("tinyint(1)"); + + b.Property("IssueId") + .HasColumnType("int"); + + b.Property("LangCode") + .HasColumnType("longtext"); + + b.Property("MarkedAsApproved") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsAvailable") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsDenied") + .HasColumnType("datetime(6)"); + + b.Property("Overview") + .HasColumnType("longtext"); + + b.Property("PosterPath") + .HasColumnType("longtext"); + + b.Property("QualityOverride") + .HasColumnType("int"); + + b.Property("ReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("RequestedByAlias") + .HasColumnType("longtext"); + + b.Property("RequestedDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestedUserId") + .HasColumnType("varchar(255)"); + + b.Property("RootPathOverride") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("longtext"); + + b.Property("TheMovieDbId") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("EpisodeCount") + .HasColumnType("int"); + + b.Property("RequestDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Background") + .HasColumnType("longtext"); + + b.Property("ExternalProviderId") + .HasColumnType("int"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("LanguageProfile") + .HasColumnType("int"); + + b.Property("Overview") + .HasColumnType("longtext"); + + b.Property("PosterPath") + .HasColumnType("longtext"); + + b.Property("QualityOverride") + .HasColumnType("int"); + + b.Property("ReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("RootFolder") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("TotalSeasons") + .HasColumnType("int"); + + b.Property("TvDbId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Token") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Agent") + .HasColumnType("int"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("RadarrQualityProfile") + .HasColumnType("int"); + + b.Property("RadarrRootPath") + .HasColumnType("int"); + + b.Property("SonarrQualityProfile") + .HasColumnType("int"); + + b.Property("SonarrQualityProfileAnime") + .HasColumnType("int"); + + b.Property("SonarrRootPath") + .HasColumnType("int"); + + b.Property("SonarrRootPathAnime") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Deleted") + .HasColumnType("tinyint(1)"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("VoteType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AirDate") + .HasColumnType("datetime(6)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("Available") + .HasColumnType("tinyint(1)"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("Requested") + .HasColumnType("tinyint(1)"); + + b.Property("SeasonId") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("Url") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ChildRequestId") + .HasColumnType("int"); + + b.Property("Overview") + .HasColumnType("longtext"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("ParentRequest"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Issues"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + + b.Navigation("IssueCategory"); + + b.Navigation("UserReported"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRequest"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Navigation("NotificationUserIds"); + + b.Navigation("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Navigation("Issues"); + + b.Navigation("SeasonRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Navigation("Comments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Navigation("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Navigation("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Navigation("Episodes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiMySql/20220210201011_MovieRequest4K.cs b/src/Ombi.Store/Migrations/OmbiMySql/20220210201011_MovieRequest4K.cs new file mode 100644 index 000000000..f9d02b876 --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiMySql/20220210201011_MovieRequest4K.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Ombi.Store.Migrations.OmbiMySql +{ + public partial class MovieRequest4K : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Has4KRequest", + table: "MovieRequests", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Has4KRequest", + table: "MovieRequests"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiMySql/20220210215019_4kMovieProperties.Designer.cs b/src/Ombi.Store/Migrations/OmbiMySql/20220210215019_4kMovieProperties.Designer.cs new file mode 100644 index 000000000..240993d15 --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiMySql/20220210215019_4kMovieProperties.Designer.cs @@ -0,0 +1,1273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context.MySql; + +#nullable disable + +namespace Ombi.Store.Migrations.OmbiMySql +{ + [DbContext(typeof(OmbiMySqlContext))] + [Migration("20220210215019_4kMovieProperties")] + partial class _4kMovieProperties + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("ProviderKey") + .HasColumnType("varchar(255)"); + + b.Property("ProviderDisplayName") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("RoleId") + .HasColumnType("varchar(255)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .HasColumnType("varchar(255)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AuditArea") + .HasColumnType("int"); + + b.Property("AuditType") + .HasColumnType("int"); + + b.Property("DateTime") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("User") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("Token") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("MobileDevices"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Agent") + .HasColumnType("int"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Message") + .HasColumnType("longtext"); + + b.Property("NotificationType") + .HasColumnType("int"); + + b.Property("Subject") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("PlayerId") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("Alias") + .HasColumnType("longtext"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("EpisodeRequestLimit") + .HasColumnType("int"); + + b.Property("EpisodeRequestLimitType") + .HasColumnType("int"); + + b.Property("Language") + .HasColumnType("longtext"); + + b.Property("LastLoggedIn") + .HasColumnType("datetime(6)"); + + b.Property("LockoutEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnd") + .HasColumnType("datetime(6)"); + + b.Property("MovieRequestLimit") + .HasColumnType("int"); + + b.Property("MovieRequestLimitType") + .HasColumnType("int"); + + b.Property("MusicRequestLimit") + .HasColumnType("int"); + + b.Property("MusicRequestLimitType") + .HasColumnType("int"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + b.Property("PhoneNumber") + .HasColumnType("longtext"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("ProviderUserId") + .HasColumnType("longtext"); + + b.Property("SecurityStamp") + .HasColumnType("longtext"); + + b.Property("StreamingCountry") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TwoFactorEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UserAccessToken") + .HasColumnType("longtext"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("AlbumId") + .HasColumnType("longtext"); + + b.Property("ContentId") + .HasColumnType("int"); + + b.Property("ContentType") + .HasColumnType("int"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Completed") + .HasColumnType("datetime(6)"); + + b.Property("Dts") + .HasColumnType("datetime(6)"); + + b.Property("Error") + .HasColumnType("longtext"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RetryCount") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("RequestQueue"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("ArtistName") + .HasColumnType("longtext"); + + b.Property("Available") + .HasColumnType("tinyint(1)"); + + b.Property("Cover") + .HasColumnType("longtext"); + + b.Property("Denied") + .HasColumnType("tinyint(1)"); + + b.Property("DeniedReason") + .HasColumnType("longtext"); + + b.Property("Disk") + .HasColumnType("longtext"); + + b.Property("ForeignAlbumId") + .HasColumnType("longtext"); + + b.Property("ForeignArtistId") + .HasColumnType("longtext"); + + b.Property("MarkedAsApproved") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsAvailable") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsDenied") + .HasColumnType("datetime(6)"); + + b.Property("Rating") + .HasColumnType("decimal(65,30)"); + + b.Property("ReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("RequestedByAlias") + .HasColumnType("longtext"); + + b.Property("RequestedDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestedUserId") + .HasColumnType("varchar(255)"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("Available") + .HasColumnType("tinyint(1)"); + + b.Property("Denied") + .HasColumnType("tinyint(1)"); + + b.Property("DeniedReason") + .HasColumnType("longtext"); + + b.Property("IssueId") + .HasColumnType("int"); + + b.Property("MarkedAsApproved") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsAvailable") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsDenied") + .HasColumnType("datetime(6)"); + + b.Property("ParentRequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("RequestedByAlias") + .HasColumnType("longtext"); + + b.Property("RequestedDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestedUserId") + .HasColumnType("varchar(255)"); + + b.Property("SeriesType") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Comment") + .HasColumnType("longtext"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("IssuesId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("IssueCategoryId") + .HasColumnType("int"); + + b.Property("IssueId") + .HasColumnType("int"); + + b.Property("ProviderId") + .HasColumnType("longtext"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("ResovledDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Subject") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("UserReportedId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("Approved4K") + .HasColumnType("tinyint(1)"); + + b.Property("Available") + .HasColumnType("tinyint(1)"); + + b.Property("Available4K") + .HasColumnType("tinyint(1)"); + + b.Property("Background") + .HasColumnType("longtext"); + + b.Property("Denied") + .HasColumnType("tinyint(1)"); + + b.Property("Denied4K") + .HasColumnType("tinyint(1)"); + + b.Property("DeniedReason") + .HasColumnType("longtext"); + + b.Property("DeniedReason4K") + .HasColumnType("longtext"); + + b.Property("DigitalReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("Has4KRequest") + .HasColumnType("tinyint(1)"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("IssueId") + .HasColumnType("int"); + + b.Property("LangCode") + .HasColumnType("longtext"); + + b.Property("MarkedAsApproved") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsApproved4K") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsAvailable") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsAvailable4K") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsDenied") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsDenied4K") + .HasColumnType("datetime(6)"); + + b.Property("Overview") + .HasColumnType("longtext"); + + b.Property("PosterPath") + .HasColumnType("longtext"); + + b.Property("QualityOverride") + .HasColumnType("int"); + + b.Property("ReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("RequestedByAlias") + .HasColumnType("longtext"); + + b.Property("RequestedDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestedDate4k") + .HasColumnType("datetime(6)"); + + b.Property("RequestedUserId") + .HasColumnType("varchar(255)"); + + b.Property("RootPathOverride") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("longtext"); + + b.Property("TheMovieDbId") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("EpisodeCount") + .HasColumnType("int"); + + b.Property("RequestDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Background") + .HasColumnType("longtext"); + + b.Property("ExternalProviderId") + .HasColumnType("int"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("LanguageProfile") + .HasColumnType("int"); + + b.Property("Overview") + .HasColumnType("longtext"); + + b.Property("PosterPath") + .HasColumnType("longtext"); + + b.Property("QualityOverride") + .HasColumnType("int"); + + b.Property("ReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("RootFolder") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("TotalSeasons") + .HasColumnType("int"); + + b.Property("TvDbId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Token") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Agent") + .HasColumnType("int"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("RadarrQualityProfile") + .HasColumnType("int"); + + b.Property("RadarrRootPath") + .HasColumnType("int"); + + b.Property("SonarrQualityProfile") + .HasColumnType("int"); + + b.Property("SonarrQualityProfileAnime") + .HasColumnType("int"); + + b.Property("SonarrRootPath") + .HasColumnType("int"); + + b.Property("SonarrRootPathAnime") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Deleted") + .HasColumnType("tinyint(1)"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("VoteType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AirDate") + .HasColumnType("datetime(6)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("Available") + .HasColumnType("tinyint(1)"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("Requested") + .HasColumnType("tinyint(1)"); + + b.Property("SeasonId") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("Url") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ChildRequestId") + .HasColumnType("int"); + + b.Property("Overview") + .HasColumnType("longtext"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("ParentRequest"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Issues"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + + b.Navigation("IssueCategory"); + + b.Navigation("UserReported"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRequest"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Navigation("NotificationUserIds"); + + b.Navigation("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Navigation("Issues"); + + b.Navigation("SeasonRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Navigation("Comments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Navigation("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Navigation("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Navigation("Episodes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiMySql/20220210215019_4kMovieProperties.cs b/src/Ombi.Store/Migrations/OmbiMySql/20220210215019_4kMovieProperties.cs new file mode 100644 index 000000000..3b599c0e0 --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiMySql/20220210215019_4kMovieProperties.cs @@ -0,0 +1,102 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Ombi.Store.Migrations.OmbiMySql +{ + public partial class _4kMovieProperties : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Approved4K", + table: "MovieRequests", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "Available4K", + table: "MovieRequests", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "Denied4K", + table: "MovieRequests", + type: "tinyint(1)", + nullable: true); + + migrationBuilder.AddColumn( + name: "DeniedReason4K", + table: "MovieRequests", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "MarkedAsApproved4K", + table: "MovieRequests", + type: "datetime(6)", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "MarkedAsAvailable4K", + table: "MovieRequests", + type: "datetime(6)", + nullable: true); + + migrationBuilder.AddColumn( + name: "MarkedAsDenied4K", + table: "MovieRequests", + type: "datetime(6)", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "RequestedDate4k", + table: "MovieRequests", + type: "datetime(6)", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Approved4K", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "Available4K", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "Denied4K", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "DeniedReason4K", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "MarkedAsApproved4K", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "MarkedAsAvailable4K", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "MarkedAsDenied4K", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "RequestedDate4k", + table: "MovieRequests"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiMySql/OmbiMySqlContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiMySql/OmbiMySqlContextModelSnapshot.cs index 8fdebc4c2..5a5c28b40 100644 --- a/src/Ombi.Store/Migrations/OmbiMySql/OmbiMySqlContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiMySql/OmbiMySqlContextModelSnapshot.cs @@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Ombi.Store.Context.MySql; +#nullable disable + namespace Ombi.Store.Migrations.OmbiMySql { [DbContext(typeof(OmbiMySqlContext))] @@ -14,8 +16,8 @@ namespace Ombi.Store.Migrations.OmbiMySql { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 64) - .HasAnnotation("ProductVersion", "5.0.1"); + .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => { @@ -40,7 +42,7 @@ namespace Ombi.Store.Migrations.OmbiMySql .IsUnique() .HasDatabaseName("RoleNameIndex"); - b.ToTable("AspNetRoles"); + b.ToTable("AspNetRoles", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -63,7 +65,7 @@ namespace Ombi.Store.Migrations.OmbiMySql b.HasIndex("RoleId"); - b.ToTable("AspNetRoleClaims"); + b.ToTable("AspNetRoleClaims", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => @@ -86,7 +88,7 @@ namespace Ombi.Store.Migrations.OmbiMySql b.HasIndex("UserId"); - b.ToTable("AspNetUserClaims"); + b.ToTable("AspNetUserClaims", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => @@ -108,7 +110,7 @@ namespace Ombi.Store.Migrations.OmbiMySql b.HasIndex("UserId"); - b.ToTable("AspNetUserLogins"); + b.ToTable("AspNetUserLogins", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => @@ -123,7 +125,7 @@ namespace Ombi.Store.Migrations.OmbiMySql b.HasIndex("RoleId"); - b.ToTable("AspNetUserRoles"); + b.ToTable("AspNetUserRoles", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => @@ -142,7 +144,7 @@ namespace Ombi.Store.Migrations.OmbiMySql b.HasKey("UserId", "LoginProvider", "Name"); - b.ToTable("AspNetUserTokens"); + b.ToTable("AspNetUserTokens", (string)null); }); modelBuilder.Entity("Ombi.Store.Entities.Audit", b => @@ -342,7 +344,7 @@ namespace Ombi.Store.Migrations.OmbiMySql .IsUnique() .HasDatabaseName("UserNameIndex"); - b.ToTable("AspNetUsers"); + b.ToTable("AspNetUsers", (string)null); }); modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => @@ -406,28 +408,6 @@ namespace Ombi.Store.Migrations.OmbiMySql b.ToTable("RequestQueue"); }); - modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("RequestId") - .HasColumnType("int"); - - b.Property("RequestType") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("varchar(255)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("RequestSubscription"); - }); - modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => { b.Property("Id") @@ -661,21 +641,36 @@ namespace Ombi.Store.Migrations.OmbiMySql b.Property("Approved") .HasColumnType("tinyint(1)"); + b.Property("Approved4K") + .HasColumnType("tinyint(1)"); + b.Property("Available") .HasColumnType("tinyint(1)"); + b.Property("Available4K") + .HasColumnType("tinyint(1)"); + b.Property("Background") .HasColumnType("longtext"); b.Property("Denied") .HasColumnType("tinyint(1)"); + b.Property("Denied4K") + .HasColumnType("tinyint(1)"); + b.Property("DeniedReason") .HasColumnType("longtext"); + b.Property("DeniedReason4K") + .HasColumnType("longtext"); + b.Property("DigitalReleaseDate") .HasColumnType("datetime(6)"); + b.Property("Has4KRequest") + .HasColumnType("tinyint(1)"); + b.Property("ImdbId") .HasColumnType("longtext"); @@ -688,12 +683,21 @@ namespace Ombi.Store.Migrations.OmbiMySql b.Property("MarkedAsApproved") .HasColumnType("datetime(6)"); + b.Property("MarkedAsApproved4K") + .HasColumnType("datetime(6)"); + b.Property("MarkedAsAvailable") .HasColumnType("datetime(6)"); + b.Property("MarkedAsAvailable4K") + .HasColumnType("datetime(6)"); + b.Property("MarkedAsDenied") .HasColumnType("datetime(6)"); + b.Property("MarkedAsDenied4K") + .HasColumnType("datetime(6)"); + b.Property("Overview") .HasColumnType("longtext"); @@ -715,6 +719,9 @@ namespace Ombi.Store.Migrations.OmbiMySql b.Property("RequestedDate") .HasColumnType("datetime(6)"); + b.Property("RequestedDate4k") + .HasColumnType("datetime(6)"); + b.Property("RequestedUserId") .HasColumnType("varchar(255)"); @@ -815,6 +822,28 @@ namespace Ombi.Store.Migrations.OmbiMySql b.ToTable("TvRequests"); }); + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => { b.Property("Id") @@ -1052,15 +1081,6 @@ namespace Ombi.Store.Migrations.OmbiMySql b.Navigation("User"); }); - modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => - { - b.HasOne("Ombi.Store.Entities.OmbiUser", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("User"); - }); - modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => { b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") @@ -1145,6 +1165,15 @@ namespace Ombi.Store.Migrations.OmbiMySql b.Navigation("User"); }); + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => { b.HasOne("Ombi.Store.Entities.OmbiUser", "User") diff --git a/src/Ombi.Store/Migrations/OmbiSqlite/20220210194758_Radarr4kRole.Designer.cs b/src/Ombi.Store/Migrations/OmbiSqlite/20220210194758_Radarr4kRole.Designer.cs new file mode 100644 index 000000000..e2c442a23 --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiSqlite/20220210194758_Radarr4kRole.Designer.cs @@ -0,0 +1,1244 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context.Sqlite; + +#nullable disable + +namespace Ombi.Store.Migrations.OmbiSqlite +{ + [DbContext(typeof(OmbiSqliteContext))] + [Migration("20220210194758_Radarr4kRole")] + partial class Radarr4kRole + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + 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("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + 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") + .IsRequired() + .HasColumnType("TEXT"); + + 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") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + 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("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuditArea") + .HasColumnType("INTEGER"); + + b.Property("AuditType") + .HasColumnType("INTEGER"); + + b.Property("DateTime") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("User") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("Token") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("MobileDevices"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Agent") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Message") + .HasColumnType("TEXT"); + + b.Property("NotificationType") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("Alias") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("EpisodeRequestLimit") + .HasColumnType("INTEGER"); + + b.Property("EpisodeRequestLimitType") + .HasColumnType("INTEGER"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LastLoggedIn") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("MovieRequestLimit") + .HasColumnType("INTEGER"); + + b.Property("MovieRequestLimitType") + .HasColumnType("INTEGER"); + + b.Property("MusicRequestLimit") + .HasColumnType("INTEGER"); + + b.Property("MusicRequestLimitType") + .HasColumnType("INTEGER"); + + 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("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("StreamingCountry") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserAccessToken") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("AlbumId") + .HasColumnType("TEXT"); + + b.Property("ContentId") + .HasColumnType("INTEGER"); + + b.Property("ContentType") + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Completed") + .HasColumnType("TEXT"); + + b.Property("Dts") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RetryCount") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RequestQueue"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("ArtistName") + .HasColumnType("TEXT"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("Cover") + .HasColumnType("TEXT"); + + b.Property("Denied") + .HasColumnType("INTEGER"); + + b.Property("DeniedReason") + .HasColumnType("TEXT"); + + b.Property("Disk") + .HasColumnType("TEXT"); + + b.Property("ForeignAlbumId") + .HasColumnType("TEXT"); + + b.Property("ForeignArtistId") + .HasColumnType("TEXT"); + + b.Property("MarkedAsApproved") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied") + .HasColumnType("TEXT"); + + b.Property("Rating") + .HasColumnType("TEXT"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("RequestedByAlias") + .HasColumnType("TEXT"); + + b.Property("RequestedDate") + .HasColumnType("TEXT"); + + b.Property("RequestedUserId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("Denied") + .HasColumnType("INTEGER"); + + b.Property("DeniedReason") + .HasColumnType("TEXT"); + + b.Property("IssueId") + .HasColumnType("INTEGER"); + + b.Property("MarkedAsApproved") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied") + .HasColumnType("TEXT"); + + b.Property("ParentRequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("RequestedByAlias") + .HasColumnType("TEXT"); + + b.Property("RequestedDate") + .HasColumnType("TEXT"); + + b.Property("RequestedUserId") + .HasColumnType("TEXT"); + + b.Property("SeriesType") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("IssuesId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDate") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IssueCategoryId") + .HasColumnType("INTEGER"); + + b.Property("IssueId") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("ResovledDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("UserReportedId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("Background") + .HasColumnType("TEXT"); + + b.Property("Denied") + .HasColumnType("INTEGER"); + + b.Property("DeniedReason") + .HasColumnType("TEXT"); + + b.Property("DigitalReleaseDate") + .HasColumnType("TEXT"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("IssueId") + .HasColumnType("INTEGER"); + + b.Property("LangCode") + .HasColumnType("TEXT"); + + b.Property("MarkedAsApproved") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied") + .HasColumnType("TEXT"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("PosterPath") + .HasColumnType("TEXT"); + + b.Property("QualityOverride") + .HasColumnType("INTEGER"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("RequestedByAlias") + .HasColumnType("TEXT"); + + b.Property("RequestedDate") + .HasColumnType("TEXT"); + + b.Property("RequestedUserId") + .HasColumnType("TEXT"); + + b.Property("RootPathOverride") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("TEXT"); + + b.Property("TheMovieDbId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EpisodeCount") + .HasColumnType("INTEGER"); + + b.Property("RequestDate") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Background") + .HasColumnType("TEXT"); + + b.Property("ExternalProviderId") + .HasColumnType("INTEGER"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("LanguageProfile") + .HasColumnType("INTEGER"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("PosterPath") + .HasColumnType("TEXT"); + + b.Property("QualityOverride") + .HasColumnType("INTEGER"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RootFolder") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TotalSeasons") + .HasColumnType("INTEGER"); + + b.Property("TvDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Token") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Agent") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RadarrQualityProfile") + .HasColumnType("INTEGER"); + + b.Property("RadarrRootPath") + .HasColumnType("INTEGER"); + + b.Property("SonarrQualityProfile") + .HasColumnType("INTEGER"); + + b.Property("SonarrQualityProfileAnime") + .HasColumnType("INTEGER"); + + b.Property("SonarrRootPath") + .HasColumnType("INTEGER"); + + b.Property("SonarrRootPathAnime") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("VoteType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AirDate") + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("Requested") + .HasColumnType("INTEGER"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Url") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChildRequestId") + .HasColumnType("INTEGER"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("ParentRequest"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Issues"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + + b.Navigation("IssueCategory"); + + b.Navigation("UserReported"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRequest"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Navigation("NotificationUserIds"); + + b.Navigation("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Navigation("Issues"); + + b.Navigation("SeasonRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Navigation("Comments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Navigation("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Navigation("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Navigation("Episodes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiSqlite/20220210194758_Radarr4kRole.cs b/src/Ombi.Store/Migrations/OmbiSqlite/20220210194758_Radarr4kRole.cs new file mode 100644 index 000000000..a97de9011 --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiSqlite/20220210194758_Radarr4kRole.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Ombi.Helpers; + +#nullable disable + +namespace Ombi.Store.Migrations.OmbiSqlite +{ + public partial class Radarr4kRole : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.InsertRole(OmbiRoles.Request4KMovie); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiSqlite/20220210200338_MovieRequest4K.Designer.cs b/src/Ombi.Store/Migrations/OmbiSqlite/20220210200338_MovieRequest4K.Designer.cs new file mode 100644 index 000000000..a2dee4283 --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiSqlite/20220210200338_MovieRequest4K.Designer.cs @@ -0,0 +1,1247 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context.Sqlite; + +#nullable disable + +namespace Ombi.Store.Migrations.OmbiSqlite +{ + [DbContext(typeof(OmbiSqliteContext))] + [Migration("20220210200338_MovieRequest4K")] + partial class MovieRequest4K + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + 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("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + 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") + .IsRequired() + .HasColumnType("TEXT"); + + 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") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + 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("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuditArea") + .HasColumnType("INTEGER"); + + b.Property("AuditType") + .HasColumnType("INTEGER"); + + b.Property("DateTime") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("User") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("Token") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("MobileDevices"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Agent") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Message") + .HasColumnType("TEXT"); + + b.Property("NotificationType") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("Alias") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("EpisodeRequestLimit") + .HasColumnType("INTEGER"); + + b.Property("EpisodeRequestLimitType") + .HasColumnType("INTEGER"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LastLoggedIn") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("MovieRequestLimit") + .HasColumnType("INTEGER"); + + b.Property("MovieRequestLimitType") + .HasColumnType("INTEGER"); + + b.Property("MusicRequestLimit") + .HasColumnType("INTEGER"); + + b.Property("MusicRequestLimitType") + .HasColumnType("INTEGER"); + + 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("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("StreamingCountry") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserAccessToken") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("AlbumId") + .HasColumnType("TEXT"); + + b.Property("ContentId") + .HasColumnType("INTEGER"); + + b.Property("ContentType") + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Completed") + .HasColumnType("TEXT"); + + b.Property("Dts") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RetryCount") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RequestQueue"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("ArtistName") + .HasColumnType("TEXT"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("Cover") + .HasColumnType("TEXT"); + + b.Property("Denied") + .HasColumnType("INTEGER"); + + b.Property("DeniedReason") + .HasColumnType("TEXT"); + + b.Property("Disk") + .HasColumnType("TEXT"); + + b.Property("ForeignAlbumId") + .HasColumnType("TEXT"); + + b.Property("ForeignArtistId") + .HasColumnType("TEXT"); + + b.Property("MarkedAsApproved") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied") + .HasColumnType("TEXT"); + + b.Property("Rating") + .HasColumnType("TEXT"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("RequestedByAlias") + .HasColumnType("TEXT"); + + b.Property("RequestedDate") + .HasColumnType("TEXT"); + + b.Property("RequestedUserId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("Denied") + .HasColumnType("INTEGER"); + + b.Property("DeniedReason") + .HasColumnType("TEXT"); + + b.Property("IssueId") + .HasColumnType("INTEGER"); + + b.Property("MarkedAsApproved") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied") + .HasColumnType("TEXT"); + + b.Property("ParentRequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("RequestedByAlias") + .HasColumnType("TEXT"); + + b.Property("RequestedDate") + .HasColumnType("TEXT"); + + b.Property("RequestedUserId") + .HasColumnType("TEXT"); + + b.Property("SeriesType") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("IssuesId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDate") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IssueCategoryId") + .HasColumnType("INTEGER"); + + b.Property("IssueId") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("ResovledDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("UserReportedId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("Background") + .HasColumnType("TEXT"); + + b.Property("Denied") + .HasColumnType("INTEGER"); + + b.Property("DeniedReason") + .HasColumnType("TEXT"); + + b.Property("DigitalReleaseDate") + .HasColumnType("TEXT"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("Is4KRequest") + .HasColumnType("INTEGER"); + + b.Property("IssueId") + .HasColumnType("INTEGER"); + + b.Property("LangCode") + .HasColumnType("TEXT"); + + b.Property("MarkedAsApproved") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied") + .HasColumnType("TEXT"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("PosterPath") + .HasColumnType("TEXT"); + + b.Property("QualityOverride") + .HasColumnType("INTEGER"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("RequestedByAlias") + .HasColumnType("TEXT"); + + b.Property("RequestedDate") + .HasColumnType("TEXT"); + + b.Property("RequestedUserId") + .HasColumnType("TEXT"); + + b.Property("RootPathOverride") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("TEXT"); + + b.Property("TheMovieDbId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EpisodeCount") + .HasColumnType("INTEGER"); + + b.Property("RequestDate") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Background") + .HasColumnType("TEXT"); + + b.Property("ExternalProviderId") + .HasColumnType("INTEGER"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("LanguageProfile") + .HasColumnType("INTEGER"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("PosterPath") + .HasColumnType("TEXT"); + + b.Property("QualityOverride") + .HasColumnType("INTEGER"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RootFolder") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TotalSeasons") + .HasColumnType("INTEGER"); + + b.Property("TvDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Token") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Agent") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RadarrQualityProfile") + .HasColumnType("INTEGER"); + + b.Property("RadarrRootPath") + .HasColumnType("INTEGER"); + + b.Property("SonarrQualityProfile") + .HasColumnType("INTEGER"); + + b.Property("SonarrQualityProfileAnime") + .HasColumnType("INTEGER"); + + b.Property("SonarrRootPath") + .HasColumnType("INTEGER"); + + b.Property("SonarrRootPathAnime") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("VoteType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AirDate") + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("Requested") + .HasColumnType("INTEGER"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Url") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChildRequestId") + .HasColumnType("INTEGER"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("ParentRequest"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Issues"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + + b.Navigation("IssueCategory"); + + b.Navigation("UserReported"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRequest"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Navigation("NotificationUserIds"); + + b.Navigation("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Navigation("Issues"); + + b.Navigation("SeasonRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Navigation("Comments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Navigation("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Navigation("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Navigation("Episodes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiSqlite/20220210200338_MovieRequest4K.cs b/src/Ombi.Store/Migrations/OmbiSqlite/20220210200338_MovieRequest4K.cs new file mode 100644 index 000000000..8d70a0358 --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiSqlite/20220210200338_MovieRequest4K.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Ombi.Store.Migrations.OmbiSqlite +{ + public partial class MovieRequest4K : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Has4KRequest", + table: "MovieRequests", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Has4KRequest", + table: "MovieRequests"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiSqlite/20220210214920_4kMovieProperties.Designer.cs b/src/Ombi.Store/Migrations/OmbiSqlite/20220210214920_4kMovieProperties.Designer.cs new file mode 100644 index 000000000..e315e78ea --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiSqlite/20220210214920_4kMovieProperties.Designer.cs @@ -0,0 +1,1271 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context.Sqlite; + +#nullable disable + +namespace Ombi.Store.Migrations.OmbiSqlite +{ + [DbContext(typeof(OmbiSqliteContext))] + [Migration("20220210214920_4kMovieProperties")] + partial class _4kMovieProperties + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + 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("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + 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") + .IsRequired() + .HasColumnType("TEXT"); + + 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") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + 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("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuditArea") + .HasColumnType("INTEGER"); + + b.Property("AuditType") + .HasColumnType("INTEGER"); + + b.Property("DateTime") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("User") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("Token") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("MobileDevices"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Agent") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Message") + .HasColumnType("TEXT"); + + b.Property("NotificationType") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("Alias") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("EpisodeRequestLimit") + .HasColumnType("INTEGER"); + + b.Property("EpisodeRequestLimitType") + .HasColumnType("INTEGER"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LastLoggedIn") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("MovieRequestLimit") + .HasColumnType("INTEGER"); + + b.Property("MovieRequestLimitType") + .HasColumnType("INTEGER"); + + b.Property("MusicRequestLimit") + .HasColumnType("INTEGER"); + + b.Property("MusicRequestLimitType") + .HasColumnType("INTEGER"); + + 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("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("StreamingCountry") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserAccessToken") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("AlbumId") + .HasColumnType("TEXT"); + + b.Property("ContentId") + .HasColumnType("INTEGER"); + + b.Property("ContentType") + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Completed") + .HasColumnType("TEXT"); + + b.Property("Dts") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RetryCount") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RequestQueue"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("ArtistName") + .HasColumnType("TEXT"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("Cover") + .HasColumnType("TEXT"); + + b.Property("Denied") + .HasColumnType("INTEGER"); + + b.Property("DeniedReason") + .HasColumnType("TEXT"); + + b.Property("Disk") + .HasColumnType("TEXT"); + + b.Property("ForeignAlbumId") + .HasColumnType("TEXT"); + + b.Property("ForeignArtistId") + .HasColumnType("TEXT"); + + b.Property("MarkedAsApproved") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied") + .HasColumnType("TEXT"); + + b.Property("Rating") + .HasColumnType("TEXT"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("RequestedByAlias") + .HasColumnType("TEXT"); + + b.Property("RequestedDate") + .HasColumnType("TEXT"); + + b.Property("RequestedUserId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("Denied") + .HasColumnType("INTEGER"); + + b.Property("DeniedReason") + .HasColumnType("TEXT"); + + b.Property("IssueId") + .HasColumnType("INTEGER"); + + b.Property("MarkedAsApproved") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied") + .HasColumnType("TEXT"); + + b.Property("ParentRequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("RequestedByAlias") + .HasColumnType("TEXT"); + + b.Property("RequestedDate") + .HasColumnType("TEXT"); + + b.Property("RequestedUserId") + .HasColumnType("TEXT"); + + b.Property("SeriesType") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("IssuesId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDate") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IssueCategoryId") + .HasColumnType("INTEGER"); + + b.Property("IssueId") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("ResovledDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("UserReportedId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("Approved4K") + .HasColumnType("INTEGER"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("Available4K") + .HasColumnType("INTEGER"); + + b.Property("Background") + .HasColumnType("TEXT"); + + b.Property("Denied") + .HasColumnType("INTEGER"); + + b.Property("Denied4K") + .HasColumnType("INTEGER"); + + b.Property("DeniedReason") + .HasColumnType("TEXT"); + + b.Property("DeniedReason4K") + .HasColumnType("TEXT"); + + b.Property("DigitalReleaseDate") + .HasColumnType("TEXT"); + + b.Property("Has4KRequest") + .HasColumnType("INTEGER"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("IssueId") + .HasColumnType("INTEGER"); + + b.Property("LangCode") + .HasColumnType("TEXT"); + + b.Property("MarkedAsApproved") + .HasColumnType("TEXT"); + + b.Property("MarkedAsApproved4K") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable4K") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied4K") + .HasColumnType("TEXT"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("PosterPath") + .HasColumnType("TEXT"); + + b.Property("QualityOverride") + .HasColumnType("INTEGER"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("RequestedByAlias") + .HasColumnType("TEXT"); + + b.Property("RequestedDate") + .HasColumnType("TEXT"); + + b.Property("RequestedDate4k") + .HasColumnType("TEXT"); + + b.Property("RequestedUserId") + .HasColumnType("TEXT"); + + b.Property("RootPathOverride") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("TEXT"); + + b.Property("TheMovieDbId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EpisodeCount") + .HasColumnType("INTEGER"); + + b.Property("RequestDate") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Background") + .HasColumnType("TEXT"); + + b.Property("ExternalProviderId") + .HasColumnType("INTEGER"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("LanguageProfile") + .HasColumnType("INTEGER"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("PosterPath") + .HasColumnType("TEXT"); + + b.Property("QualityOverride") + .HasColumnType("INTEGER"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RootFolder") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TotalSeasons") + .HasColumnType("INTEGER"); + + b.Property("TvDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Token") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Agent") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RadarrQualityProfile") + .HasColumnType("INTEGER"); + + b.Property("RadarrRootPath") + .HasColumnType("INTEGER"); + + b.Property("SonarrQualityProfile") + .HasColumnType("INTEGER"); + + b.Property("SonarrQualityProfileAnime") + .HasColumnType("INTEGER"); + + b.Property("SonarrRootPath") + .HasColumnType("INTEGER"); + + b.Property("SonarrRootPathAnime") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("VoteType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AirDate") + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("Requested") + .HasColumnType("INTEGER"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Url") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChildRequestId") + .HasColumnType("INTEGER"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("ParentRequest"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Issues"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + + b.Navigation("IssueCategory"); + + b.Navigation("UserReported"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRequest"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Navigation("NotificationUserIds"); + + b.Navigation("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Navigation("Issues"); + + b.Navigation("SeasonRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Navigation("Comments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Navigation("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Navigation("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Navigation("Episodes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiSqlite/20220210214920_4kMovieProperties.cs b/src/Ombi.Store/Migrations/OmbiSqlite/20220210214920_4kMovieProperties.cs new file mode 100644 index 000000000..38f010eae --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiSqlite/20220210214920_4kMovieProperties.cs @@ -0,0 +1,101 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Ombi.Store.Migrations.OmbiSqlite +{ + public partial class _4kMovieProperties : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Approved4K", + table: "MovieRequests", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "Available4K", + table: "MovieRequests", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "Denied4K", + table: "MovieRequests", + type: "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + name: "DeniedReason4K", + table: "MovieRequests", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "MarkedAsApproved4K", + table: "MovieRequests", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "MarkedAsAvailable4K", + table: "MovieRequests", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "MarkedAsDenied4K", + table: "MovieRequests", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "RequestedDate4k", + table: "MovieRequests", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Approved4K", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "Available4K", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "Denied4K", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "DeniedReason4K", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "MarkedAsApproved4K", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "MarkedAsAvailable4K", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "MarkedAsDenied4K", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "RequestedDate4k", + table: "MovieRequests"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiSqlite/OmbiSqliteContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiSqlite/OmbiSqliteContextModelSnapshot.cs index 46a00353e..528989d02 100644 --- a/src/Ombi.Store/Migrations/OmbiSqlite/OmbiSqliteContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiSqlite/OmbiSqliteContextModelSnapshot.cs @@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Ombi.Store.Context.Sqlite; +#nullable disable + namespace Ombi.Store.Migrations.OmbiSqlite { [DbContext(typeof(OmbiSqliteContext))] @@ -13,8 +15,7 @@ namespace Ombi.Store.Migrations.OmbiSqlite protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.1"); + modelBuilder.HasAnnotation("ProductVersion", "6.0.0"); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => { @@ -39,7 +40,7 @@ namespace Ombi.Store.Migrations.OmbiSqlite .IsUnique() .HasDatabaseName("RoleNameIndex"); - b.ToTable("AspNetRoles"); + b.ToTable("AspNetRoles", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -62,7 +63,7 @@ namespace Ombi.Store.Migrations.OmbiSqlite b.HasIndex("RoleId"); - b.ToTable("AspNetRoleClaims"); + b.ToTable("AspNetRoleClaims", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => @@ -85,7 +86,7 @@ namespace Ombi.Store.Migrations.OmbiSqlite b.HasIndex("UserId"); - b.ToTable("AspNetUserClaims"); + b.ToTable("AspNetUserClaims", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => @@ -107,7 +108,7 @@ namespace Ombi.Store.Migrations.OmbiSqlite b.HasIndex("UserId"); - b.ToTable("AspNetUserLogins"); + b.ToTable("AspNetUserLogins", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => @@ -122,7 +123,7 @@ namespace Ombi.Store.Migrations.OmbiSqlite b.HasIndex("RoleId"); - b.ToTable("AspNetUserRoles"); + b.ToTable("AspNetUserRoles", (string)null); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => @@ -141,7 +142,7 @@ namespace Ombi.Store.Migrations.OmbiSqlite b.HasKey("UserId", "LoginProvider", "Name"); - b.ToTable("AspNetUserTokens"); + b.ToTable("AspNetUserTokens", (string)null); }); modelBuilder.Entity("Ombi.Store.Entities.Audit", b => @@ -341,7 +342,7 @@ namespace Ombi.Store.Migrations.OmbiSqlite .IsUnique() .HasDatabaseName("UserNameIndex"); - b.ToTable("AspNetUsers"); + b.ToTable("AspNetUsers", (string)null); }); modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => @@ -405,28 +406,6 @@ namespace Ombi.Store.Migrations.OmbiSqlite b.ToTable("RequestQueue"); }); - modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("RequestId") - .HasColumnType("INTEGER"); - - b.Property("RequestType") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("RequestSubscription"); - }); - modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => { b.Property("Id") @@ -660,21 +639,36 @@ namespace Ombi.Store.Migrations.OmbiSqlite b.Property("Approved") .HasColumnType("INTEGER"); + b.Property("Approved4K") + .HasColumnType("INTEGER"); + b.Property("Available") .HasColumnType("INTEGER"); + b.Property("Available4K") + .HasColumnType("INTEGER"); + b.Property("Background") .HasColumnType("TEXT"); b.Property("Denied") .HasColumnType("INTEGER"); + b.Property("Denied4K") + .HasColumnType("INTEGER"); + b.Property("DeniedReason") .HasColumnType("TEXT"); + b.Property("DeniedReason4K") + .HasColumnType("TEXT"); + b.Property("DigitalReleaseDate") .HasColumnType("TEXT"); + b.Property("Has4KRequest") + .HasColumnType("INTEGER"); + b.Property("ImdbId") .HasColumnType("TEXT"); @@ -687,12 +681,21 @@ namespace Ombi.Store.Migrations.OmbiSqlite b.Property("MarkedAsApproved") .HasColumnType("TEXT"); + b.Property("MarkedAsApproved4K") + .HasColumnType("TEXT"); + b.Property("MarkedAsAvailable") .HasColumnType("TEXT"); + b.Property("MarkedAsAvailable4K") + .HasColumnType("TEXT"); + b.Property("MarkedAsDenied") .HasColumnType("TEXT"); + b.Property("MarkedAsDenied4K") + .HasColumnType("TEXT"); + b.Property("Overview") .HasColumnType("TEXT"); @@ -714,6 +717,9 @@ namespace Ombi.Store.Migrations.OmbiSqlite b.Property("RequestedDate") .HasColumnType("TEXT"); + b.Property("RequestedDate4k") + .HasColumnType("TEXT"); + b.Property("RequestedUserId") .HasColumnType("TEXT"); @@ -814,6 +820,28 @@ namespace Ombi.Store.Migrations.OmbiSqlite b.ToTable("TvRequests"); }); + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => { b.Property("Id") @@ -1051,15 +1079,6 @@ namespace Ombi.Store.Migrations.OmbiSqlite b.Navigation("User"); }); - modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => - { - b.HasOne("Ombi.Store.Entities.OmbiUser", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("User"); - }); - modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => { b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") @@ -1144,6 +1163,15 @@ namespace Ombi.Store.Migrations.OmbiSqlite b.Navigation("User"); }); + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => { b.HasOne("Ombi.Store.Entities.OmbiUser", "User") diff --git a/src/Ombi.Store/Repository/EmbyContentRepository.cs b/src/Ombi.Store/Repository/EmbyContentRepository.cs index 19bab7f76..ddec14025 100644 --- a/src/Ombi.Store/Repository/EmbyContentRepository.cs +++ b/src/Ombi.Store/Repository/EmbyContentRepository.cs @@ -25,7 +25,6 @@ // ************************************************************************/ #endregion -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -37,7 +36,6 @@ namespace Ombi.Store.Repository { public class EmbyContentRepository : MediaServerContentRepository, IEmbyContentRepository { - public EmbyContentRepository(ExternalContext db):base(db) { } @@ -97,7 +95,13 @@ namespace Ombi.Store.Repository { Db.EmbyContent.Update((EmbyContent)existingContent); } - + + public override Task UpdateRange(IEnumerable existingContent) + { + Db.EmbyContent.UpdateRange((EmbyContent)existingContent); + return InternalSaveChanges(); + } + public override RecentlyAddedType RecentlyAddedType => RecentlyAddedType.Emby; } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/IMediaServerContentRepository.cs b/src/Ombi.Store/Repository/IMediaServerContentRepository.cs index 73cc00fde..db4835a16 100644 --- a/src/Ombi.Store/Repository/IMediaServerContentRepository.cs +++ b/src/Ombi.Store/Repository/IMediaServerContentRepository.cs @@ -10,6 +10,7 @@ namespace Ombi.Store.Repository { RecentlyAddedType RecentlyAddedType{ get; } Task Update(IMediaServerContent existingContent); + Task UpdateRange(IEnumerable existingContent); IQueryable GetAllEpisodes(); Task Add(IMediaServerEpisode content); Task AddRange(IEnumerable content); diff --git a/src/Ombi.Store/Repository/JellyfinContentRepository.cs b/src/Ombi.Store/Repository/JellyfinContentRepository.cs index 28cdcfae8..02acee5b0 100644 --- a/src/Ombi.Store/Repository/JellyfinContentRepository.cs +++ b/src/Ombi.Store/Repository/JellyfinContentRepository.cs @@ -98,6 +98,12 @@ namespace Ombi.Store.Repository Db.JellyfinContent.Update((JellyfinContent)existingContent); } + public override Task UpdateRange(IEnumerable existingContent) + { + Db.JellyfinContent.UpdateRange((JellyfinContent)existingContent); + return InternalSaveChanges(); + } + public override RecentlyAddedType RecentlyAddedType => RecentlyAddedType.Jellyfin; } } diff --git a/src/Ombi.Store/Repository/MediaServerRepository.cs b/src/Ombi.Store/Repository/MediaServerRepository.cs index 0f458ee2b..c1c7d4ec8 100644 --- a/src/Ombi.Store/Repository/MediaServerRepository.cs +++ b/src/Ombi.Store/Repository/MediaServerRepository.cs @@ -21,5 +21,6 @@ namespace Ombi.Store.Repository public abstract Task Add(IMediaServerEpisode content); public abstract Task AddRange(IEnumerable content); public abstract void UpdateWithoutSave(IMediaServerContent existingContent); + public abstract Task UpdateRange(IEnumerable existingContent); } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/PlexContentRepository.cs b/src/Ombi.Store/Repository/PlexContentRepository.cs index b99ba157f..be79d1a29 100644 --- a/src/Ombi.Store/Repository/PlexContentRepository.cs +++ b/src/Ombi.Store/Repository/PlexContentRepository.cs @@ -164,5 +164,10 @@ namespace Ombi.Store.Repository await InternalSaveChanges(); } + public override Task UpdateRange(IEnumerable existingContent) + { + Db.PlexServerContent.UpdateRange((PlexServerContent)existingContent); + return InternalSaveChanges(); + } } } \ No newline at end of file diff --git a/src/Ombi.sln b/src/Ombi.sln index 841f84dbd..a92df055e 100644 --- a/src/Ombi.sln +++ b/src/Ombi.sln @@ -126,6 +126,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.RottenTomatoes", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.I18n", "Ombi.I18n\Ombi.I18n.csproj", "{6A922D57-8622-4C36-8E6E-D5BA9E8DA6C0}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.MediaServer", "Ombi.Api.MediaServer\Ombi.Api.MediaServer.csproj", "{AFC0BA9B-E38D-479F-825A-2F94EE4D6120}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -439,6 +441,12 @@ Global {6A922D57-8622-4C36-8E6E-D5BA9E8DA6C0}.NonUiBuild|Any CPU.Build.0 = NonUiBuild|Any CPU {6A922D57-8622-4C36-8E6E-D5BA9E8DA6C0}.Release|Any CPU.ActiveCfg = Release|Any CPU {6A922D57-8622-4C36-8E6E-D5BA9E8DA6C0}.Release|Any CPU.Build.0 = Release|Any CPU + {AFC0BA9B-E38D-479F-825A-2F94EE4D6120}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFC0BA9B-E38D-479F-825A-2F94EE4D6120}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFC0BA9B-E38D-479F-825A-2F94EE4D6120}.NonUiBuild|Any CPU.ActiveCfg = NonUiBuild|Any CPU + {AFC0BA9B-E38D-479F-825A-2F94EE4D6120}.NonUiBuild|Any CPU.Build.0 = NonUiBuild|Any CPU + {AFC0BA9B-E38D-479F-825A-2F94EE4D6120}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFC0BA9B-E38D-479F-825A-2F94EE4D6120}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -487,6 +495,7 @@ Global {E2186FDA-D827-4781-8663-130AC382F12C} = {9293CA11-360A-4C20-A674-B9E794431BF5} {5DE40A66-B369-469E-8626-ECE23D9D8034} = {9293CA11-360A-4C20-A674-B9E794431BF5} {8F19C701-7881-4BC7-8BBA-B068A6B954AD} = {9293CA11-360A-4C20-A674-B9E794431BF5} + {AFC0BA9B-E38D-479F-825A-2F94EE4D6120} = {9293CA11-360A-4C20-A674-B9E794431BF5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {192E9BF8-00B4-45E4-BCCC-4C215725C869} diff --git a/src/Ombi/ClientApp/src/app/app.module.ts b/src/Ombi/ClientApp/src/app/app.module.ts index 082ce80d2..735dce36b 100644 --- a/src/Ombi/ClientApp/src/app/app.module.ts +++ b/src/Ombi/ClientApp/src/app/app.module.ts @@ -21,6 +21,8 @@ import { CustomPageComponent } from "./custompage/custompage.component"; import { CustomizationState } from "./state/customization/customization.state"; import { DataViewModule } from "primeng/dataview"; import { DialogModule } from "primeng/dialog"; +import { FEATURES_INITIALIZER } from "./state/features/features-initializer"; +import { FeatureState } from "./state/features"; import { JwtModule } from "@auth0/angular-jwt"; import { LandingPageComponent } from "./landingpage/landingpage.component"; import { LandingPageService } from "./services"; @@ -38,6 +40,8 @@ import { MatInputModule } from "@angular/material/input"; import { MatListModule } from '@angular/material/list'; import { MatMenuModule } from "@angular/material/menu"; import { MatNativeDateModule } from '@angular/material/core'; +import { MatPaginatorI18n } from "./localization/MatPaginatorI18n"; +import { MatPaginatorIntl } from "@angular/material/paginator"; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatSidenavModule } from '@angular/material/sidenav'; import { MatSlideToggleModule } from "@angular/material/slide-toggle"; @@ -63,11 +67,9 @@ import { StorageService } from "./shared/storage/storage-service"; import { TokenResetPasswordComponent } from "./login/tokenresetpassword.component"; import { TooltipModule } from "primeng/tooltip"; import { TranslateHttpLoader } from "@ngx-translate/http-loader"; +import { TranslateService } from "@ngx-translate/core"; import { UnauthorizedInterceptor } from "./auth/unauthorized.interceptor"; import { environment } from "../environments/environment"; -import { MatPaginatorIntl } from "@angular/material/paginator"; -import { TranslateService } from "@ngx-translate/core"; -import { MatPaginatorI18n } from "./localization/MatPaginatorI18n"; const routes: Routes = [ { path: "*", component: PageNotFoundComponent }, @@ -162,10 +164,10 @@ export function JwtTokenGetter() { }), SidebarModule, MatNativeDateModule, MatIconModule, MatSidenavModule, MatListModule, MatToolbarModule, LayoutModule, MatSlideToggleModule, - NgxsModule.forRoot([CustomizationState], { + NgxsModule.forRoot([CustomizationState, FeatureState], { developmentMode: !environment.production, }), - ...environment.production ? [] : + ...environment.production ? [] : [ NgxsReduxDevtoolsPluginModule.forRoot(), ] @@ -205,6 +207,7 @@ export function JwtTokenGetter() { StorageService, RequestService, SignalRNotificationService, + FEATURES_INITIALIZER, CUSTOMIZATION_INITIALIZER, { provide: APP_BASE_HREF, diff --git a/src/Ombi/ClientApp/src/app/discover/components/actor/discover-actor.component.html b/src/Ombi/ClientApp/src/app/discover/components/actor/discover-actor.component.html index 67f243503..87db00955 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/actor/discover-actor.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/actor/discover-actor.component.html @@ -5,7 +5,7 @@
- +
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/discover/components/actor/discover-actor.component.ts b/src/Ombi/ClientApp/src/app/discover/components/actor/discover-actor.component.ts index 290042d87..5c333522c 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/actor/discover-actor.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/actor/discover-actor.component.ts @@ -1,4 +1,4 @@ -import { Component } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { SearchV2Service } from "../../../services"; import { IActorCredits, IActorCast } from "../../../interfaces/ISearchTvResultV2"; @@ -6,30 +6,31 @@ import { IDiscoverCardResult } from "../../interfaces"; import { RequestType } from "../../../interfaces"; import { AuthService } from "../../../auth/auth.service"; import { forkJoin } from "rxjs"; -import { isEqual } from "lodash"; +import { FeaturesFacade } from "../../../state/features/features.facade"; @Component({ templateUrl: "./discover-actor.component.html", styleUrls: ["./discover-actor.component.scss"], }) -export class DiscoverActorComponent { +export class DiscoverActorComponent implements OnInit { public actorId: number; public loadingFlag: boolean; public isAdmin: boolean; + public is4kEnabled = false; public discoverResults: IDiscoverCardResult[] = []; constructor(private searchService: SearchV2Service, private route: ActivatedRoute, - private auth: AuthService) { + private auth: AuthService, + private featureService: FeaturesFacade) { this.route.params.subscribe((params: any) => { this.actorId = params.actorId; - this.isAdmin = this.auth.isAdmin(); - this.search(); }); } - - private search() { + ngOnInit() { + this.isAdmin = this.auth.isAdmin(); + this.is4kEnabled = this.featureService.is4kEnabled(); this.discoverResults = []; this.loading(); diff --git a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.html b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.html index 5a6993e44..b7593902f 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.html @@ -20,12 +20,24 @@
-
-
+
+ + + + + +
+
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.scss b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.scss index 6bf6748bf..38cd4501e 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.scss +++ b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.scss @@ -292,4 +292,8 @@ a.poster-overlay:hover{ .btn-ombi{ background-color:#293a4c; +} + +::ng-deep .mat-menu-panel { + min-width: 190px !important; } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.ts b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.ts index 4de883256..470559b0d 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.ts @@ -21,6 +21,7 @@ export class DiscoverCardComponent implements OnInit { @Input() public discoverType: DiscoverType; @Input() public result: IDiscoverCardResult; @Input() public isAdmin: boolean; + @Input() public is4kEnabled: boolean = false; public RequestType = RequestType; public hide: boolean; public fullyLoaded = false; @@ -111,7 +112,7 @@ export class DiscoverCardComponent implements OnInit { return ""; } - public request(event: any) { + public request(event: any, is4k: boolean) { event.preventDefault(); this.loading = true; switch (this.result.type) { @@ -121,14 +122,15 @@ export class DiscoverCardComponent implements OnInit { return; case RequestType.movie: if (this.isAdmin) { - const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.movie, id: this.result.id }, panelClass: 'modal-panel' }); + const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.movie, id: this.result.id, }, panelClass: 'modal-panel' }); dialog.afterClosed().subscribe((result) => { if (result) { this.requestService.requestMovie({ theMovieDbId: +this.result.id, languageCode: this.translate.currentLang, qualityPathOverride: result.radarrPathId, requestOnBehalf: result.username?.id, - rootFolderOverride: result.radarrFolderId, }).subscribe(x => { + rootFolderOverride: result.radarrFolderId, + is4KRequest: is4k }).subscribe(x => { if (x.result) { this.result.requested = true; this.messageService.send(this.translate.instant("Requests.RequestAddedSuccessfully", { title: this.result.title }), "Ok"); @@ -139,7 +141,7 @@ export class DiscoverCardComponent implements OnInit { } }); } else { - this.requestService.requestMovie({ theMovieDbId: +this.result.id, languageCode: this.translate.currentLang, requestOnBehalf: null, qualityPathOverride: null, rootFolderOverride: null }).subscribe(x => { + this.requestService.requestMovie({ theMovieDbId: +this.result.id, languageCode: this.translate.currentLang, requestOnBehalf: null, qualityPathOverride: null, rootFolderOverride: null, is4KRequest: is4k }).subscribe(x => { if (x.result) { this.result.requested = true; this.messageService.send(this.translate.instant("Requests.RequestAddedSuccessfully", { title: this.result.title }), "Ok"); diff --git a/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.html b/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.html index aa9ffc5f2..5be2aa1ad 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.html @@ -8,6 +8,6 @@ - + \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.ts b/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.ts index a00146ad5..9617c993e 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.ts @@ -5,6 +5,7 @@ import { SearchV2Service } from "../../../services"; import { StorageService } from "../../../shared/storage/storage-service"; import { MatButtonToggleChange } from '@angular/material/button-toggle'; import { Carousel } from 'primeng/carousel'; +import { FeaturesFacade } from "../../../state/features/features.facade"; export enum DiscoverType { Upcoming, @@ -36,6 +37,7 @@ export class CarouselListComponent implements OnInit { public RequestType = RequestType; public loadingFlag: boolean; public DiscoverType = DiscoverType; + public is4kEnabled = false; get mediaTypeStorageKey() { return "DiscoverOptions" + this.discoverType.toString(); @@ -44,7 +46,8 @@ export class CarouselListComponent implements OnInit { private currentlyLoaded = 0; constructor(private searchService: SearchV2Service, - private storageService: StorageService) { + private storageService: StorageService, + private featureFacade: FeaturesFacade) { this.responsiveOptions = [ { breakpoint: '4000px', @@ -135,6 +138,7 @@ export class CarouselListComponent implements OnInit { } public async ngOnInit() { + this.is4kEnabled = this.featureFacade.is4kEnabled(); this.currentlyLoaded = 0; const localDiscoverOptions = +this.storageService.get(this.mediaTypeStorageKey); if (localDiscoverOptions) { diff --git a/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.html b/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.html index 23a7791f0..0750a12c0 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.html @@ -16,7 +16,7 @@
- +
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.ts b/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.ts index 9e84ea7d0..d643c52af 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.ts @@ -8,6 +8,7 @@ import { IDiscoverCardResult } from "../../interfaces"; import { IMovieCollectionsViewModel } from "../../../interfaces/ISearchTvResultV2"; import { RequestServiceV2 } from "../../../services/requestV2.service"; import { RequestType } from "../../../interfaces"; +import { FeaturesFacade } from "../../../state/features/features.facade"; @Component({ templateUrl: "./discover-collections.component.html", @@ -19,6 +20,7 @@ export class DiscoverCollectionsComponent implements OnInit { public collection: IMovieCollectionsViewModel; public loadingFlag: boolean; public isAdmin: boolean; + public is4kEnabled = false; public discoverResults: IDiscoverCardResult[] = []; @@ -27,13 +29,15 @@ export class DiscoverCollectionsComponent implements OnInit { private requestServiceV2: RequestServiceV2, private messageService: MessageService, private auth: AuthService, - private translate: TranslateService) { + private translate: TranslateService, + private featureFacade: FeaturesFacade) { this.route.params.subscribe((params: any) => { this.collectionId = params.collectionId; }); } public async ngOnInit() { + this.is4kEnabled = this.featureFacade.is4kEnabled(); this.loadingFlag = true; this.isAdmin = this.auth.isAdmin(); this.collection = await this.searchService.getMovieCollections(this.collectionId); diff --git a/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.html b/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.html index 4ef9255fb..b1911ceee 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.html @@ -4,7 +4,7 @@
- +
diff --git a/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.ts b/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.ts index f0258b97d..a8544365d 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.ts @@ -10,6 +10,7 @@ import { SearchFilter } from "../../../my-nav/SearchFilter"; import { SearchV2Service } from "../../../services"; import { StorageService } from "../../../shared/storage/storage-service"; import { isEqual } from "lodash"; +import { FeaturesFacade } from "../../../state/features/features.facade"; @Component({ templateUrl: "./search-results.component.html", @@ -21,6 +22,7 @@ export class DiscoverSearchResultsComponent implements OnInit { public searchTerm: string; public results: IMultiSearchResult[]; public isAdmin: boolean; + public is4kEnabled = false; public discoverResults: IDiscoverCardResult[] = []; @@ -34,7 +36,8 @@ export class DiscoverSearchResultsComponent implements OnInit { private router: Router, private advancedDataService: AdvancedSearchDialogDataService, private store: StorageService, - private authService: AuthService) { + private authService: AuthService, + private featureFacade: FeaturesFacade) { this.route.params.subscribe((params: any) => { this.isAdvancedSearch = this.router.url === '/discover/advanced/search'; if (this.isAdvancedSearch) { @@ -53,6 +56,7 @@ export class DiscoverSearchResultsComponent implements OnInit { } public async ngOnInit() { + this.is4kEnabled = this.featureFacade.is4kEnabled(); this.isAdmin = this.authService.isAdmin(); this.filterService.onFilterChange.subscribe(async x => { if (!isEqual(this.filter, x)) { diff --git a/src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts b/src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts index 440e0d5b2..1ccbd355b 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts @@ -16,6 +16,12 @@ export interface IMovieRequests extends IFullBaseRequest { subscribed: boolean; showSubscribe: boolean; requestStatus: string; + has4KRequest: boolean; + approved4K: boolean; + available4K: boolean; + denied4K: boolean; + deniedReason4K: string; + requestedDate4k: Date; // For the UI rootPathOverrideTitle: string; @@ -53,6 +59,7 @@ export interface IRequestsViewModel { export interface IMovieUpdateModel { id: number; + is4K: boolean; } export interface IDenyMovieModel extends IMovieUpdateModel { @@ -176,6 +183,7 @@ export interface IEpisodesRequests { export interface IMovieRequestModel extends BaseRequestOptions { theMovieDbId: number; languageCode: string | undefined; + is4KRequest?: boolean; } export interface IFilter { diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISearchMovieResultV2.ts b/src/Ombi/ClientApp/src/app/interfaces/ISearchMovieResultV2.ts index da4b27f32..d4faf5bfe 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISearchMovieResultV2.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISearchMovieResultV2.ts @@ -42,6 +42,11 @@ externalIds: IExternalIds; keywords: IKeywords; belongsToCollection: ICollectionsModel; + has4KRequest: boolean; + approved4K: boolean; + available4K: boolean; + denied4K: boolean; + deniedReason4K: string; // for the UI requestProcessing: boolean; diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts index c7990215f..8bf2755ec 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts @@ -158,6 +158,11 @@ export interface IRadarrSettings extends IExternalSettings { scanForAvailability: boolean; } +export interface IRadarrCombined { + radarr: IRadarrSettings; + radarr4K: IRadarrSettings; +} + export interface ILidarrSettings extends IExternalSettings { enabled: boolean; apiKey: string; diff --git a/src/Ombi/ClientApp/src/app/interfaces/IUser.ts b/src/Ombi/ClientApp/src/app/interfaces/IUser.ts index 97882e783..069b1e506 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/IUser.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/IUser.ts @@ -146,3 +146,8 @@ export enum INotificationAgent { Webhook = 9, WhatsApp = 10 } + +export interface IFeatureEnablement { + name: string; + enabled: boolean; +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html index 2e807547c..2a29b30e6 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html @@ -22,9 +22,11 @@ [isAdmin]="isAdmin" [canShowAdvanced]="showAdvanced && movieRequest" [type]="requestType" + [has4KRequest]="movie.has4KRequest" (openTrailer)="openDialog()" (onAdvancedOptions)="openAdvancedOptions()" - (onReProcessRequest)="reProcessRequest()" + (onReProcessRequest)="reProcessRequest(false)" + (onReProcess4KRequest)="reProcessRequest(true)" > @@ -54,6 +56,7 @@ + @@ -65,40 +68,85 @@ - - - - - + + + + + + + + + + + + + + + + + + + + + + - - + + + - + + + + + + + + - - + + +
+
+ {{'MediaDetails.Quality' | translate }}  + {{"4K" | quality}} +
+
{{'MediaDetails.RootFolderOverride' | translate }}
{{request.rootPathOverrideTitle}}
diff --git a/src/Ombi/ClientApp/src/app/media-details/components/shared/deny-dialog/deny-dialog.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/shared/deny-dialog/deny-dialog.component.ts index 3132ac84f..caa799794 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/shared/deny-dialog/deny-dialog.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/shared/deny-dialog/deny-dialog.component.ts @@ -4,6 +4,7 @@ import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; import { RequestService, MessageService } from "../../../../services"; import { TranslateService } from "@ngx-translate/core"; import { RequestType, IRequestEngineResult } from "../../../../interfaces"; +import { firstValueFrom } from "rxjs"; @Component({ selector: "deny-dialog", @@ -22,13 +23,13 @@ export class DenyDialogComponent { public async deny() { let result: IRequestEngineResult; if(this.data.requestType == RequestType.movie) { - result = await this.requestService.denyMovie({id: this.data.requestId, reason: this.denyReason }).toPromise(); + result = await firstValueFrom(this.requestService.denyMovie({id: this.data.requestId, reason: this.denyReason, is4K: this.data.is4K })); } if(this.data.requestType == RequestType.tvShow) { - result = await this.requestService.denyChild({id: this.data.requestId, reason: this.denyReason }).toPromise(); + result = await firstValueFrom(this.requestService.denyChild({id: this.data.requestId, reason: this.denyReason })); } if(this.data.requestType == RequestType.album) { - result = await this.requestService.denyAlbum({id: this.data.requestId, reason: this.denyReason }).toPromise(); + result = await firstValueFrom(this.requestService.denyAlbum({id: this.data.requestId, reason: this.denyReason })); } if (result.result) { diff --git a/src/Ombi/ClientApp/src/app/media-details/components/shared/interfaces/interfaces.ts b/src/Ombi/ClientApp/src/app/media-details/components/shared/interfaces/interfaces.ts index 4237386c4..215a82ace 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/shared/interfaces/interfaces.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/shared/interfaces/interfaces.ts @@ -4,6 +4,7 @@ export interface IDenyDialogData { requestType: RequestType; requestId: number; denied: boolean; + is4K: boolean; } export interface IIssueDialogData { diff --git a/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.html b/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.html index f05792aa9..98bb999b2 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.html @@ -35,9 +35,13 @@ {{ 'MediaDetails.RadarrConfiguration' | translate}} {{ 'MediaDetails.SonarrConfiguration' | translate}} - +
diff --git a/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.ts index bc504347b..9215c1464 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.ts @@ -23,10 +23,12 @@ export class SocialIconsComponent { @Input() isAdmin: boolean; @Input() canShowAdvanced: boolean; + @Input() has4KRequest: boolean; @Output() openTrailer: EventEmitter = new EventEmitter(); @Output() onAdvancedOptions: EventEmitter = new EventEmitter(); @Output() onReProcessRequest: EventEmitter = new EventEmitter(); + @Output() onReProcess4KRequest: EventEmitter = new EventEmitter(); public RequestType = RequestType; @@ -39,7 +41,11 @@ export class SocialIconsComponent { this.onAdvancedOptions.emit(); } - public reProcessRequest() { - this.onReProcessRequest.emit(); + public reProcessRequest(is4K: boolean) { + if (is4K) { + this.onReProcess4KRequest.emit(); + } else { + this.onReProcessRequest.emit(); + } } } diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-requests/tv-requests-panel.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-requests/tv-requests-panel.component.ts index 10eee3989..2de85814c 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-requests/tv-requests-panel.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-requests/tv-requests-panel.component.ts @@ -100,7 +100,7 @@ export class TvRequestsPanelComponent { } public reProcessRequest(request: IChildRequests) { - this.requestService2.reprocessRequest(request.id, RequestType.tvShow).subscribe(result => { + this.requestService2.reprocessRequest(request.id, RequestType.tvShow, false).subscribe(result => { if (result.result) { this.messageService.send(result.message ? result.message : "Successfully Re-processed the request", "Ok"); } else { diff --git a/src/Ombi/ClientApp/src/app/pipes/QualityPipe.ts b/src/Ombi/ClientApp/src/app/pipes/QualityPipe.ts index 393e08640..c1f9a4211 100644 --- a/src/Ombi/ClientApp/src/app/pipes/QualityPipe.ts +++ b/src/Ombi/ClientApp/src/app/pipes/QualityPipe.ts @@ -6,6 +6,9 @@ export class QualityPipe implements PipeTransform { if (value.toUpperCase() === "4K" || value.toUpperCase() === "8K") { return value; } + if (value[value.length - 1].toUpperCase() === "P") { + return value; + } return value + "p"; } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/requests-list/components/albums-grid/albums-grid.component.ts b/src/Ombi/ClientApp/src/app/requests-list/components/albums-grid/albums-grid.component.ts index d0529e018..a58d8cd9f 100644 --- a/src/Ombi/ClientApp/src/app/requests-list/components/albums-grid/albums-grid.component.ts +++ b/src/Ombi/ClientApp/src/app/requests-list/components/albums-grid/albums-grid.component.ts @@ -1,14 +1,14 @@ -import { Component, AfterViewInit, ViewChild, EventEmitter, Output, ChangeDetectorRef, OnInit } from "@angular/core"; -import { IRequestsViewModel, IAlbumRequest } from "../../../interfaces"; -import { MatPaginator } from "@angular/material/paginator"; -import { MatSort } from "@angular/material/sort"; -import { merge, Observable, of as observableOf } from 'rxjs'; +import { AfterViewInit, ChangeDetectorRef, Component, EventEmitter, OnInit, Output, ViewChild } from "@angular/core"; +import { IAlbumRequest, IRequestsViewModel } from "../../../interfaces"; +import { Observable, merge, of as observableOf } from 'rxjs'; import { catchError, map, startWith, switchMap } from 'rxjs/operators'; -import { RequestServiceV2 } from "../../../services/requestV2.service"; import { AuthService } from "../../../auth/auth.service"; -import { StorageService } from "../../../shared/storage/storage-service"; +import { MatPaginator } from "@angular/material/paginator"; +import { MatSort } from "@angular/material/sort"; import { RequestFilterType } from "../../models/RequestFilterType"; +import { RequestServiceV2 } from "../../../services/requestV2.service"; +import { StorageService } from "../../../shared/storage/storage-service"; @Component({ templateUrl: "./albums-grid.component.html", @@ -130,7 +130,7 @@ export class AlbumsGridComponent implements OnInit, AfterViewInit { this.ref.detectChanges(); }; - const data = { request: request, filter: filter, onChange: onChange, manageOwnRequests: this.manageOwnRequests, isAdmin: this.isAdmin }; + const data = { request: request, filter: filter, onChange: onChange, manageOwnRequests: this.manageOwnRequests, isAdmin: this.isAdmin, has4kRequest: false }; this.onOpenOptions.emit(data); } diff --git a/src/Ombi/ClientApp/src/app/requests-list/components/movies-grid/movies-grid.component.html b/src/Ombi/ClientApp/src/app/requests-list/components/movies-grid/movies-grid.component.html index 61baa3eaa..d9366e7d1 100644 --- a/src/Ombi/ClientApp/src/app/requests-list/components/movies-grid/movies-grid.component.html +++ b/src/Ombi/ClientApp/src/app/requests-list/components/movies-grid/movies-grid.component.html @@ -58,7 +58,7 @@ {{ 'Requests.RequestDate' | translate}} - {{element.requestedDate | amLocal | amUserLocale | amDateFormat: 'LL'}} + {{getRequestDate(element) | amLocal | amUserLocale | amDateFormat: 'LL'}} @@ -66,6 +66,14 @@ {{element.status |translateStatus }} + + {{ 'Requests.Has4KRequest' | translate}} + + + + + + {{ 'Requests.RequestStatus' | translate}} @@ -92,4 +100,5 @@ + \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/requests-list/components/movies-grid/movies-grid.component.ts b/src/Ombi/ClientApp/src/app/requests-list/components/movies-grid/movies-grid.component.ts index 111c299c5..b3283bb73 100644 --- a/src/Ombi/ClientApp/src/app/requests-list/components/movies-grid/movies-grid.component.ts +++ b/src/Ombi/ClientApp/src/app/requests-list/components/movies-grid/movies-grid.component.ts @@ -59,6 +59,9 @@ export class MoviesGridComponent implements OnInit, AfterViewInit { this.manageOwnRequests = this.auth.hasRole("ManageOwnRequests") if (this.isAdmin) { this.displayedColumns.unshift('select'); + this.displayedColumns.splice(4,0,'has4kRequest'); + } else if (this.auth.hasRole("Request4KMovie")) { + this.displayedColumns.splice(4,0,'has4kRequest'); } const defaultCount = this.storageService.get(this.storageKeyGridCount); const defaultSort = this.storageService.get(this.storageKey); @@ -139,7 +142,7 @@ export class MoviesGridComponent implements OnInit, AfterViewInit { this.ref.detectChanges(); }; - const data = { request: request, filter: filter, onChange: onChange, manageOwnRequests: this.manageOwnRequests, isAdmin: this.isAdmin }; + const data = { request: request, filter: filter, onChange: onChange, manageOwnRequests: this.manageOwnRequests, isAdmin: this.isAdmin, has4kRequest: request.has4KRequest }; this.onOpenOptions.emit(data); } @@ -176,13 +179,17 @@ export class MoviesGridComponent implements OnInit, AfterViewInit { }); } - public bulkApprove() { + public bulkApprove = () => this.bulkApproveInternal(false); + + public bulkApprove4K = () => this.bulkApproveInternal(true); + + private bulkApproveInternal(is4k: boolean) { if (this.selection.isEmpty()) { return; } let tasks = new Array>(); this.selection.selected.forEach((selected) => { - tasks.push(this.requestServiceV1.approveMovie({ id: selected.id })); + tasks.push(this.requestServiceV1.approveMovie({ id: selected.id, is4K: is4k })); }); this.isLoadingResults = true; @@ -199,4 +206,11 @@ export class MoviesGridComponent implements OnInit, AfterViewInit { this.ngAfterViewInit(); }) } + + public getRequestDate(request: IMovieRequests) : Date { + if (new Date(request.requestedDate).getFullYear() === 1) { + return request.requestedDate4k; + } + return request.requestedDate; + } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/requests-list/components/options/request-options.component.html b/src/Ombi/ClientApp/src/app/requests-list/components/options/request-options.component.html index ac974ece8..b584cc0c5 100644 --- a/src/Ombi/ClientApp/src/app/requests-list/components/options/request-options.component.html +++ b/src/Ombi/ClientApp/src/app/requests-list/components/options/request-options.component.html @@ -5,6 +5,9 @@ {{'Requests.RequestPanel.Approve' | translate}} + + {{'Requests.RequestPanel.Approve4K' | translate}} + {{'Requests.RequestPanel.ChangeAvailability' | translate}} diff --git a/src/Ombi/ClientApp/src/app/requests-list/components/options/request-options.component.ts b/src/Ombi/ClientApp/src/app/requests-list/components/options/request-options.component.ts index aa98943ca..a37680bc2 100644 --- a/src/Ombi/ClientApp/src/app/requests-list/components/options/request-options.component.ts +++ b/src/Ombi/ClientApp/src/app/requests-list/components/options/request-options.component.ts @@ -4,7 +4,7 @@ import { MessageService, RequestService } from '../../../services'; import { IRequestEngineResult, RequestType } from '../../../interfaces'; import { UpdateType } from '../../models/UpdateType'; import { TranslateService } from '@ngx-translate/core'; -import { Observable } from 'rxjs'; +import { firstValueFrom, Observable } from 'rxjs'; @Component({ selector: 'request-options', @@ -15,7 +15,7 @@ export class RequestOptionsComponent { public RequestType = RequestType; constructor(@Inject(MAT_BOTTOM_SHEET_DATA) public data: any, - private requestService: RequestService, + private requestService: RequestService, private messageService: MessageService, private bottomSheetRef: MatBottomSheetRef, private translate: TranslateService) { } @@ -44,25 +44,34 @@ export class RequestOptionsComponent { public async approve() { if (this.data.type === RequestType.movie) { - await this.requestService.approveMovie({id: this.data.id}).toPromise(); + await firstValueFrom(this.requestService.approveMovie({id: this.data.id, is4K: false})); } if (this.data.type === RequestType.tvShow) { - await this.requestService.approveChild({id: this.data.id}).toPromise(); + await firstValueFrom(this.requestService.approveChild({id: this.data.id})); } if (this.data.type === RequestType.album) { - await this.requestService.approveAlbum({id: this.data.id}).toPromise(); + await firstValueFrom(this.requestService.approveAlbum({id: this.data.id})); } this.bottomSheetRef.dismiss({type: UpdateType.Approve}); return; } + public async approve4K() { + if (this.data.type != RequestType.movie) { + return; + } + + await firstValueFrom(this.requestService.approveMovie({id: this.data.id, is4K: true})); + } + public async changeAvailability() { if (this.data.type === RequestType.movie) { - await this.requestService.markMovieAvailable({id: this.data.id}).toPromise(); + // TODO 4K + await firstValueFrom(this.requestService.markMovieAvailable({id: this.data.id, is4K: false})) } if (this.data.type === RequestType.album) { - await this.requestService.markAlbumAvailable({id: this.data.id}).toPromise(); + await firstValueFrom(this.requestService.markAlbumAvailable({id: this.data.id})); } this.bottomSheetRef.dismiss({type: UpdateType.Availability}); diff --git a/src/Ombi/ClientApp/src/app/requests-list/components/requests-list.component.ts b/src/Ombi/ClientApp/src/app/requests-list/components/requests-list.component.ts index 351c5a794..e5bf34fa4 100644 --- a/src/Ombi/ClientApp/src/app/requests-list/components/requests-list.component.ts +++ b/src/Ombi/ClientApp/src/app/requests-list/components/requests-list.component.ts @@ -1,7 +1,6 @@ import { Component, ViewChild } from "@angular/core"; import { MatBottomSheet } from "@angular/material/bottom-sheet"; -import { MoviesGridComponent } from "./movies-grid/movies-grid.component"; import { RequestOptionsComponent } from "./options/request-options.component"; import { UpdateType } from "../models/UpdateType"; @@ -13,8 +12,8 @@ export class RequestsListComponent { constructor(private bottomSheet: MatBottomSheet) { } - public onOpenOptions(event: { request: any, filter: any, onChange: any, manageOwnRequests: boolean, isAdmin: boolean }) { - const ref = this.bottomSheet.open(RequestOptionsComponent, { data: { id: event.request.id, type: event.request.requestType, canApprove: event.request.canApprove, manageOwnRequests: event.manageOwnRequests, isAdmin: event.isAdmin } }); + public onOpenOptions(event: { request: any, filter: any, onChange: any, manageOwnRequests: boolean, isAdmin: boolean, has4kRequest: boolean }) { + const ref = this.bottomSheet.open(RequestOptionsComponent, { data: { id: event.request.id, type: event.request.requestType, canApprove: event.request.canApprove, manageOwnRequests: event.manageOwnRequests, isAdmin: event.isAdmin, has4kRequest: event.has4kRequest } }); ref.afterDismissed().subscribe((result) => { if(!result) { diff --git a/src/Ombi/ClientApp/src/app/requests-list/components/tv-grid/tv-grid.component.ts b/src/Ombi/ClientApp/src/app/requests-list/components/tv-grid/tv-grid.component.ts index 5e8521302..1d84fe3f0 100644 --- a/src/Ombi/ClientApp/src/app/requests-list/components/tv-grid/tv-grid.component.ts +++ b/src/Ombi/ClientApp/src/app/requests-list/components/tv-grid/tv-grid.component.ts @@ -1,14 +1,14 @@ -import { Component, AfterViewInit, ViewChild, Output, EventEmitter, ChangeDetectorRef, OnInit } from "@angular/core"; -import { IRequestsViewModel, IChildRequests } from "../../../interfaces"; -import { MatPaginator } from "@angular/material/paginator"; -import { MatSort } from "@angular/material/sort"; -import { merge, of as observableOf, Observable } from 'rxjs'; +import { AfterViewInit, ChangeDetectorRef, Component, EventEmitter, OnInit, Output, ViewChild } from "@angular/core"; +import { IChildRequests, IRequestsViewModel } from "../../../interfaces"; +import { Observable, merge, of as observableOf } from 'rxjs'; import { catchError, map, startWith, switchMap } from 'rxjs/operators'; -import { RequestServiceV2 } from "../../../services/requestV2.service"; import { AuthService } from "../../../auth/auth.service"; -import { StorageService } from "../../../shared/storage/storage-service"; +import { MatPaginator } from "@angular/material/paginator"; +import { MatSort } from "@angular/material/sort"; import { RequestFilterType } from "../../models/RequestFilterType"; +import { RequestServiceV2 } from "../../../services/requestV2.service"; +import { StorageService } from "../../../shared/storage/storage-service"; @Component({ templateUrl: "./tv-grid.component.html", @@ -108,7 +108,7 @@ export class TvGridComponent implements OnInit, AfterViewInit { this.ref.detectChanges(); }; - const data = { request: request, filter: filter, onChange: onChange, manageOwnRequests: this.manageOwnRequests, isAdmin: this.isAdmin }; + const data = { request: request, filter: filter, onChange: onChange, manageOwnRequests: this.manageOwnRequests, isAdmin: this.isAdmin, has4kRequest: false }; this.onOpenOptions.emit(data); } diff --git a/src/Ombi/ClientApp/src/app/services/feature.service.ts b/src/Ombi/ClientApp/src/app/services/feature.service.ts new file mode 100644 index 000000000..18a9cf86f --- /dev/null +++ b/src/Ombi/ClientApp/src/app/services/feature.service.ts @@ -0,0 +1,27 @@ +import { APP_BASE_HREF } from "@angular/common"; +import { Injectable, Inject } from "@angular/core"; + +import { HttpClient } from "@angular/common/http"; +import { Observable } from "rxjs"; + +import { IFeatureEnablement } from "../interfaces"; +import { ServiceHelpers } from "./service.helpers"; + +@Injectable({ providedIn: "root" }) +export class FeatureService extends ServiceHelpers { + constructor(http: HttpClient, @Inject(APP_BASE_HREF) href:string) { + super(http, "/api/v2/Features/", href); + } + + public getFeatures(): Observable { + return this.http.get(this.url, {headers: this.headers}); + } + + public enable(feature: IFeatureEnablement): Observable { + return this.http.post(`${this.url}enable`, JSON.stringify(feature), {headers: this.headers}); + } + + public disable(feature: IFeatureEnablement): Observable { + return this.http.post(`${this.url}disable`, JSON.stringify(feature), {headers: this.headers}); + } +} diff --git a/src/Ombi/ClientApp/src/app/services/requestV2.service.ts b/src/Ombi/ClientApp/src/app/services/requestV2.service.ts index 433547b8f..62ff11aef 100644 --- a/src/Ombi/ClientApp/src/app/services/requestV2.service.ts +++ b/src/Ombi/ClientApp/src/app/services/requestV2.service.ts @@ -93,8 +93,8 @@ export class RequestServiceV2 extends ServiceHelpers { return this.http.post(`${this.url}TV/`, JSON.stringify(tv), {headers: this.headers}); } - public reprocessRequest(requestId: number, type: RequestType): Observable { - return this.http.post(`${this.url}reprocess/${type}/${requestId}`, undefined, { headers: this.headers }); + public reprocessRequest(requestId: number, type: RequestType, is4K: boolean): Observable { + return this.http.post(`${this.url}reprocess/${type}/${requestId}/${is4K}`, undefined, { headers: this.headers }); } public requestMovieCollection(collectionId: number): Observable { diff --git a/src/Ombi/ClientApp/src/app/services/settings.service.ts b/src/Ombi/ClientApp/src/app/services/settings.service.ts index 3f4409d0c..4758c8382 100644 --- a/src/Ombi/ClientApp/src/app/services/settings.service.ts +++ b/src/Ombi/ClientApp/src/app/services/settings.service.ts @@ -39,6 +39,7 @@ import { IVoteSettings, ITwilioSettings, IWebhookNotificationSettings, + IRadarrCombined, } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; @@ -101,11 +102,11 @@ export class SettingsService extends ServiceHelpers { return this.http.post(`${this.url}/Sonarr`, JSON.stringify(settings), {headers: this.headers}); } - public getRadarr(): Observable { - return this.http.get(`${this.url}/Radarr`, {headers: this.headers}); + public getRadarr(): Observable { + return this.http.get(`${this.url}/Radarr`, {headers: this.headers}); } - public saveRadarr(settings: IRadarrSettings): Observable { + public saveRadarr(settings: IRadarrCombined): Observable { return this.http.post(`${this.url}/Radarr`, JSON.stringify(settings), {headers: this.headers}); } diff --git a/src/Ombi/ClientApp/src/app/settings/features/features.component.html b/src/Ombi/ClientApp/src/app/settings/features/features.component.html new file mode 100644 index 000000000..4eb975cfe --- /dev/null +++ b/src/Ombi/ClientApp/src/app/settings/features/features.component.html @@ -0,0 +1,20 @@ + + +
+ Features + +
+
+
+
+
+ +
+
+

{{feature.name}}

+
+
+
+
+
+
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/settings/features/features.component.scss b/src/Ombi/ClientApp/src/app/settings/features/features.component.scss new file mode 100644 index 000000000..658d13101 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/settings/features/features.component.scss @@ -0,0 +1,5 @@ +.small-middle-container { + margin: auto; + width: 95%; + margin-top: 10px; +} diff --git a/src/Ombi/ClientApp/src/app/settings/features/features.component.ts b/src/Ombi/ClientApp/src/app/settings/features/features.component.ts new file mode 100644 index 000000000..75a155b6c --- /dev/null +++ b/src/Ombi/ClientApp/src/app/settings/features/features.component.ts @@ -0,0 +1,32 @@ +import { Component, OnInit } from "@angular/core"; + +import { FeaturesFacade } from "../../state/features"; +import { IFeatureEnablement } from "../../interfaces"; +import { MatSlideToggleChange } from "@angular/material/slide-toggle"; +import { firstValueFrom } from "rxjs"; + +@Component({ + templateUrl: "./features.component.html", + styleUrls: ["./features.component.scss"] +}) +export class FeaturesComponent implements OnInit { + + public features: IFeatureEnablement[]; + + constructor(private readonly featuresFacade: FeaturesFacade) { } + + public async ngOnInit() { + this.featuresFacade.features$().subscribe(x => { + this.features = x; + }); + + } + + public updateFeature(change: MatSlideToggleChange, feature: IFeatureEnablement) { + if (change.checked) { + firstValueFrom(this.featuresFacade.enable(feature)); + } else { + firstValueFrom(this.featuresFacade.disable(feature)); + } + } +} diff --git a/src/Ombi/ClientApp/src/app/settings/radarr/components/radarr-form.component.html b/src/Ombi/ClientApp/src/app/settings/radarr/components/radarr-form.component.html new file mode 100644 index 000000000..2dbf6869d --- /dev/null +++ b/src/Ombi/ClientApp/src/app/settings/radarr/components/radarr-form.component.html @@ -0,0 +1,101 @@ +
+
+
+
+
+ Enable +
+
+ Scan for Availability +
+
+ + Do not search for Movies + +
+
+
+
+
+
+
+ +
+ + Hostname or IP + + + + Port + + + + SSL + +
+
+
+ + API key + + +
+
+ + Base URL + + +
+
+
+ +
+
+ +
+
+ + Quality Profiles + + + {{quality.name}} + + + + +
+
+
+ +
+
+ + Default Root Folder + + + {{folder.path}} + + + + +
+
+ + Default Minimum Availability + + + {{min.name}} + + + +
+
+
+
+
+
+ +
+
+
+
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/settings/radarr/components/radarr-form.component.scss b/src/Ombi/ClientApp/src/app/settings/radarr/components/radarr-form.component.scss new file mode 100644 index 000000000..bf4c9e420 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/settings/radarr/components/radarr-form.component.scss @@ -0,0 +1,21 @@ +@import "~styles/shared.scss"; +.small-middle-container { + margin: auto; + width: 95%; + margin-top: 10px; +} + +.col-8 { + display: inline-table; +} +.col-md-5 { + display: inline-table; +} + +.row { + display: block; +} + +.top-spacing { + margin-top: 10px; +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/settings/radarr/components/radarr-form.component.ts b/src/Ombi/ClientApp/src/app/settings/radarr/components/radarr-form.component.ts new file mode 100644 index 000000000..f426f273b --- /dev/null +++ b/src/Ombi/ClientApp/src/app/settings/radarr/components/radarr-form.component.ts @@ -0,0 +1,92 @@ +import { ChangeDetectionStrategy, Component, OnInit } from "@angular/core"; +import { ControlContainer, FormGroup, Validators } from "@angular/forms"; + +import { IMinimumAvailability, IRadarrProfile, IRadarrRootFolder, IRadarrSettings } from "../../../interfaces"; +import { TesterService, NotificationService, RadarrService } from "../../../services"; + + +@Component({ + selector: "ombi-settings-radarr-form", + templateUrl: "./radarr-form.component.html", + styleUrls: ["./radarr-form.component.scss"], + // changeDetection: ChangeDetectionStrategy.OnPush +}) +export class RadarrFormComponent implements OnInit { + + public qualities: IRadarrProfile[]; + public rootFolders: IRadarrRootFolder[]; + public minimumAvailabilityOptions: IMinimumAvailability[]; + public profilesRunning: boolean; + public rootFoldersRunning: boolean; + public form: FormGroup; + + constructor(private radarrService: RadarrService, + private notificationService: NotificationService, + private testerService: TesterService, + private controlContainer: ControlContainer) { } + + public ngOnInit() { + this.form = this.controlContainer.control; + // this.toggleValidators(); + + this.qualities = []; + this.qualities.push({ name: "Please Select", id: -1 }); + + this.rootFolders = []; + this.rootFolders.push({ path: "Please Select", id: -1 }); + this.minimumAvailabilityOptions = [ + { name: "Announced", value: "Announced" }, + { name: "In Cinemas", value: "InCinemas" }, + { name: "Physical / Web", value: "Released" }, + ]; + } + + public toggleValidators() { + const enabled = this.form.controls.enabled.value as boolean; + this.form.controls.apiKey.setValidators(enabled ? [Validators.required] : null); + this.form.controls.defaultQualityProfile.setValidators(enabled ? [Validators.required] : null); + this.form.controls.defaultRootPath.setValidators(enabled ? [Validators.required] : null); + this.form.controls.ip.setValidators(enabled ? [Validators.required] : null); + this.form.controls.port.setValidators(enabled ? [Validators.required] : null); + this.form.controls.minimumAvailability.setValidators(enabled ? [Validators.required] : null); + } + + public getProfiles(form: FormGroup) { + this.profilesRunning = true; + this.radarrService.getQualityProfiles(form.value).subscribe(x => { + this.qualities = x; + this.qualities.unshift({ name: "Please Select", id: -1 }); + + this.profilesRunning = false; + this.notificationService.success("Successfully retrieved the Quality Profiles"); + }); + } + + public getRootFolders(form: FormGroup) { + this.rootFoldersRunning = true; + this.radarrService.getRootFolders(form.value).subscribe(x => { + this.rootFolders = x; + this.rootFolders.unshift({ path: "Please Select", id: -1 }); + + this.rootFoldersRunning = false; + this.notificationService.success("Successfully retrieved the Root Folders"); + }); + } + + public test(form: FormGroup) { + if (form.invalid) { + this.notificationService.error("Please check your entered values"); + return; + } + const settings = form.value; + this.testerService.radarrTest(settings).subscribe(result => { + if (result.isValid) { + this.notificationService.success("Successfully connected to Radarr!"); + } else if (result.expectedSubDir) { + this.notificationService.error("Your Radarr Base URL must be set to " + result.expectedSubDir); + } else { + this.notificationService.error("We could not connect to Radarr!"); + } + }); + } +} diff --git a/src/Ombi/ClientApp/src/app/settings/radarr/radarr.component.html b/src/Ombi/ClientApp/src/app/settings/radarr/radarr.component.html index b6d30998a..724beccf1 100644 --- a/src/Ombi/ClientApp/src/app/settings/radarr/radarr.component.html +++ b/src/Ombi/ClientApp/src/app/settings/radarr/radarr.component.html @@ -4,107 +4,30 @@ Radarr Settings
-
-
-
-
- Enable -
-
- Scan for Availability -
-
- - Do not search for Movies - -
-
-
-
-
-
-
- -
- - Hostname or IP - - - - Port - - - - SSL - -
-
-
- - API key - - -
-
- - Base URL - - -
-
-
- -
-
- -
-
- - Quality Profiles - - - {{quality.name}} - - - -
-
-
- -
-
- - Default Root Folder - - - {{folder.path}} - - - + + + + + + + + + + -
-
- - Default Minimum Availability - - - {{min.name}} - - - -
-
-
-
-
-
- -
-
- -
-
+ + +
+
diff --git a/src/Ombi/ClientApp/src/app/settings/radarr/radarr.component.ts b/src/Ombi/ClientApp/src/app/settings/radarr/radarr.component.ts index 9e787b406..1e2925f13 100644 --- a/src/Ombi/ClientApp/src/app/settings/radarr/radarr.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/radarr/radarr.component.ts @@ -1,12 +1,9 @@ import { Component, OnInit } from "@angular/core"; -import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { FormBuilder, FormGroup } from "@angular/forms"; -import { IMinimumAvailability, IRadarrProfile, IRadarrRootFolder } from "../../interfaces"; -import { IRadarrSettings } from "../../interfaces"; -import { RadarrService } from "../../services"; -import { TesterService } from "../../services"; -import { NotificationService } from "../../services"; -import { SettingsService } from "../../services"; +import { IMinimumAvailability, IRadarrCombined, IRadarrProfile, IRadarrRootFolder } from "../../interfaces"; +import { NotificationService, SettingsService } from "../../services"; +import { FeaturesFacade } from "../../state/features/features.facade"; @Component({ templateUrl: "./radarr.component.html", @@ -20,102 +17,67 @@ export class RadarrComponent implements OnInit { public profilesRunning: boolean; public rootFoldersRunning: boolean; public form: FormGroup; + public is4kEnabled: boolean = false; constructor(private settingsService: SettingsService, - private radarrService: RadarrService, private notificationService: NotificationService, - private fb: FormBuilder, - private testerService: TesterService) { } + private featureFacade: FeaturesFacade, + private fb: FormBuilder) { } public ngOnInit() { + this.is4kEnabled = this.featureFacade.is4kEnabled(); this.settingsService.getRadarr() .subscribe(x => { - this.form = this.fb.group({ - enabled: [x.enabled], - apiKey: [x.apiKey, [Validators.required]], - defaultQualityProfile: [+x.defaultQualityProfile, [Validators.required]], - defaultRootPath: [x.defaultRootPath, [Validators.required]], - ssl: [x.ssl], - subDir: [x.subDir], - ip: [x.ip, [Validators.required]], - port: [x.port, [Validators.required]], - addOnly: [x.addOnly], - minimumAvailability: [x.minimumAvailability, [Validators.required]], - scanForAvailability: [x.scanForAvailability] + radarr: this.fb.group({ + enabled: [x.radarr.enabled], + apiKey: [x.radarr.apiKey], + defaultQualityProfile: [+x.radarr.defaultQualityProfile], + defaultRootPath: [x.radarr.defaultRootPath], + ssl: [x.radarr.ssl], + subDir: [x.radarr.subDir], + ip: [x.radarr.ip], + port: [x.radarr.port], + addOnly: [x.radarr.addOnly], + minimumAvailability: [x.radarr.minimumAvailability], + scanForAvailability: [x.radarr.scanForAvailability] + }), + radarr4K: this.fb.group({ + enabled: [x.radarr4K.enabled], + apiKey: [x.radarr4K.apiKey], + defaultQualityProfile: [+x.radarr4K.defaultQualityProfile], + defaultRootPath: [x.radarr4K.defaultRootPath], + ssl: [x.radarr4K.ssl], + subDir: [x.radarr4K.subDir], + ip: [x.radarr4K.ip], + port: [x.radarr4K.port], + addOnly: [x.radarr4K.addOnly], + minimumAvailability: [x.radarr4K.minimumAvailability], + scanForAvailability: [x.radarr4K.scanForAvailability] + }), }); - - if (x.defaultQualityProfile) { - this.getProfiles(this.form); - } - if (x.defaultRootPath) { - this.getRootFolders(this.form); - } }); - - this.qualities = []; - this.qualities.push({ name: "Please Select", id: -1 }); - - this.rootFolders = []; - this.rootFolders.push({ path: "Please Select", id: -1 }); - this.minimumAvailabilityOptions = [ - { name: "Announced", value: "Announced" }, - { name: "In Cinemas", value: "InCinemas" }, - { name: "Physical / Web", value: "Released" }, - ]; - } - public getProfiles(form: FormGroup) { - this.profilesRunning = true; - this.radarrService.getQualityProfiles(form.value).subscribe(x => { - this.qualities = x; - this.qualities.unshift({ name: "Please Select", id: -1 }); - this.profilesRunning = false; - this.notificationService.success("Successfully retrieved the Quality Profiles"); - }); - } - - public getRootFolders(form: FormGroup) { - this.rootFoldersRunning = true; - this.radarrService.getRootFolders(form.value).subscribe(x => { - this.rootFolders = x; - this.rootFolders.unshift({ path: "Please Select", id: -1 }); - - this.rootFoldersRunning = false; - this.notificationService.success("Successfully retrieved the Root Folders"); - }); - } - - public test(form: FormGroup) { + public onSubmit(form: FormGroup) { if (form.invalid) { this.notificationService.error("Please check your entered values"); return; } - const settings = form.value; - this.testerService.radarrTest(settings).subscribe(result => { - if (result.isValid) { - this.notificationService.success("Successfully connected to Radarr!"); - } else if (result.expectedSubDir) { - this.notificationService.error("Your Radarr Base URL must be set to " + result.expectedSubDir); - } else { - this.notificationService.error("We could not connect to Radarr!"); - } - }); - } + const radarrForm = form.controls.radarr as FormGroup; + const radarr4KForm = form.controls.radarr4K as FormGroup; -public onSubmit(form: FormGroup) { - if (form.invalid) { - this.notificationService.error("Please check your entered values"); + if (radarrForm.controls.enabled.value && (radarrForm.controls.defaultQualityProfile.value === -1 || radarrForm.controls.defaultRootPath.value === "Please Select")) { + this.notificationService.error("Please check your entered values for Radarr"); return; } - if (form.controls.defaultQualityProfile.value === "-1" || form.controls.defaultRootPath.value === "Please Select") { - this.notificationService.error("Please check your entered values"); + if (radarr4KForm.controls.enabled.value && (radarr4KForm.controls.defaultQualityProfile.value === -1 || radarr4KForm.controls.defaultRootPath.value === "Please Select")) { + this.notificationService.error("Please check your entered values for Radarr 4K"); return; } - const settings = form.value; + const settings = form.value; this.settingsService.saveRadarr(settings).subscribe(x => { if (x) { this.notificationService.success("Successfully saved Radarr settings"); diff --git a/src/Ombi/ClientApp/src/app/settings/settings.module.ts b/src/Ombi/ClientApp/src/app/settings/settings.module.ts index b486e5b26..144322194 100644 --- a/src/Ombi/ClientApp/src/app/settings/settings.module.ts +++ b/src/Ombi/ClientApp/src/app/settings/settings.module.ts @@ -1,76 +1,88 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; +import { + CouchPotatoService, + EmbyService, + FileDownloadService, + IssuesService, + JellyfinService, + JobService, + LidarrService, + MobileService, + NotificationMessageService, + PlexService, + RadarrService, + RequestRetryService, + SonarrService, + SystemService, + TesterService, + TheMovieDbService, + ValidationService +} from "../services"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { RouterModule, Routes } from "@angular/router"; -// import { TagInputModule } from "ngx-chips"; -import { ClipboardModule } from "ngx-clipboard"; +import { AboutComponent } from "./about/about.component"; import { AuthGuard } from "../auth/auth.guard"; import { AuthService } from "../auth/auth.service"; -import { - CouchPotatoService, EmbyService, JellyfinService, IssuesService, JobService, LidarrService, MobileService, NotificationMessageService, PlexService, RadarrService, - RequestRetryService, SonarrService, TesterService, ValidationService, SystemService, FileDownloadService, TheMovieDbService -} from "../services"; - -import { PipeModule } from "../pipes/pipe.module"; -import { AboutComponent } from "./about/about.component"; import { AuthenticationComponent } from "./authentication/authentication.component"; +import {AutoCompleteModule} from "primeng/autocomplete"; +import {CalendarModule} from "primeng/calendar"; +import { ClipboardModule } from "ngx-clipboard"; +import { CloudMobileComponent } from "./notifications/cloudmobile.coponent"; +import { CloudMobileService } from "../services/cloudmobile.service"; +import { CommonModule } from "@angular/common"; import { CouchPotatoComponent } from "./couchpotato/couchpotato.component"; import { CustomizationComponent } from "./customization/customization.component"; +import {DialogModule} from "primeng/dialog"; +import { DiscordComponent } from "./notifications/discord.component"; import { DogNzbComponent } from "./dognzb/dognzb.component"; +import { EmailNotificationComponent } from "./notifications/emailnotification.component"; import { EmbyComponent } from "./emby/emby.component"; -import { JellyfinComponent } from "./jellyfin/jellyfin.component"; import { FailedRequestsComponent } from "./failedrequests/failedrequests.component"; +import { FeaturesComponent } from "./features/features.component"; +import { GotifyComponent } from "./notifications/gotify.component"; +import { HubService } from "../services/hub.service"; +import {InputSwitchModule} from "primeng/inputswitch"; +import {InputTextModule} from "primeng/inputtext"; import { IssuesComponent } from "./issues/issues.component"; +import { JellyfinComponent } from "./jellyfin/jellyfin.component"; import { JobsComponent } from "./jobs/jobs.component"; import { LandingPageComponent } from "./landingpage/landingpage.component"; import { LidarrComponent } from "./lidarr/lidarr.component"; +import { LogsComponent } from "./logs/logs.component"; import { MassEmailComponent } from "./massemail/massemail.component"; -import { DiscordComponent } from "./notifications/discord.component"; -import { EmailNotificationComponent } from "./notifications/emailnotification.component"; -import { GotifyComponent } from "./notifications/gotify.component"; +import { MatDialogModule } from "@angular/material/dialog"; +import { MatMenuModule } from "@angular/material/menu"; import { MattermostComponent } from "./notifications/mattermost.component"; +import {MenuModule} from "primeng/menu"; import { MobileComponent } from "./notifications/mobile.component"; import { NewsletterComponent } from "./notifications/newsletter.component"; +import { NgModule } from "@angular/core"; import { NotificationTemplate } from "./notifications/notificationtemplate.component"; -import { PushbulletComponent } from "./notifications/pushbullet.component"; -import { PushoverComponent } from "./notifications/pushover.component"; -import { SlackComponent } from "./notifications/slack.component"; -import { TelegramComponent } from "./notifications/telegram.component"; -import { WebhookComponent } from "./notifications/webhook.component"; import { OmbiComponent } from "./ombi/ombi.component"; +import { PipeModule } from "../pipes/pipe.module"; import { PlexComponent } from "./plex/plex.component"; +import { PushbulletComponent } from "./notifications/pushbullet.component"; +import { PushoverComponent } from "./notifications/pushover.component"; import { RadarrComponent } from "./radarr/radarr.component"; +import { RadarrFormComponent } from "./radarr/components/radarr-form.component"; +import {RadioButtonModule} from "primeng/radiobutton"; +import { SettingsMenuComponent } from "./settingsmenu.component"; +import { SharedModule } from "../shared/shared.module"; import { SickRageComponent } from "./sickrage/sickrage.component"; +import { SlackComponent } from "./notifications/slack.component"; import { SonarrComponent } from "./sonarr/sonarr.component"; +import { TelegramComponent } from "./notifications/telegram.component"; import { TheMovieDbComponent } from "./themoviedb/themoviedb.component"; +import {TooltipModule} from "primeng/tooltip"; +import { TwilioComponent } from "./notifications/twilio/twilio.component"; import { UpdateComponent } from "./update/update.component"; +import { UpdateDialogComponent } from "./about/update-dialog.component"; +import { UpdateService } from "../services/update.service"; import { UserManagementComponent } from "./usermanagement/usermanagement.component"; import { VoteComponent } from "./vote/vote.component"; -import { WikiComponent } from "./wiki.component"; - -import { SettingsMenuComponent } from "./settingsmenu.component"; - -import {AutoCompleteModule } from "primeng/autocomplete"; -import {CalendarModule } from "primeng/calendar"; -import {InputSwitchModule } from "primeng/inputswitch"; -import {InputTextModule } from "primeng/inputtext"; -import {DialogModule } from "primeng/dialog"; -import {MenuModule } from "primeng/menu"; -import {RadioButtonModule } from "primeng/radiobutton"; -import {TooltipModule } from "primeng/tooltip"; - -import { MatMenuModule } from "@angular/material/menu"; -import { SharedModule } from "../shared/shared.module"; -import { HubService } from "../services/hub.service"; -import { LogsComponent } from "./logs/logs.component"; -import { TwilioComponent } from "./notifications/twilio/twilio.component"; +import { WebhookComponent } from "./notifications/webhook.component"; import { WhatsAppComponent } from "./notifications/twilio/whatsapp.component"; -import { CloudMobileComponent } from "./notifications/cloudmobile.coponent"; -import { CloudMobileService } from "../services/cloudmobile.service"; -import { UpdateService } from "../services/update.service"; -import { MatDialogModule } from "@angular/material/dialog"; -import { UpdateDialogComponent } from "./about/update-dialog.component"; +import { WikiComponent } from "./wiki.component"; const routes: Routes = [ { path: "Ombi", component: OmbiComponent, canActivate: [AuthGuard] }, @@ -109,6 +121,7 @@ const routes: Routes = [ { path: "FailedRequests", component: FailedRequestsComponent, canActivate: [AuthGuard] }, { path: "Logs", component: LogsComponent, canActivate: [AuthGuard] }, { path: "CloudMobile", component: CloudMobileComponent, canActivate: [AuthGuard] }, + { path: "Features", component: FeaturesComponent, canActivate: [AuthGuard] }, ]; @NgModule({ @@ -145,6 +158,7 @@ const routes: Routes = [ SonarrComponent, SlackComponent, RadarrComponent, + RadarrFormComponent, EmailNotificationComponent, NotificationTemplate, PushoverComponent, @@ -172,6 +186,7 @@ const routes: Routes = [ LogsComponent, TwilioComponent, WhatsAppComponent, + FeaturesComponent, CloudMobileComponent, UpdateDialogComponent, ], diff --git a/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.html b/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.html index bd6949ebb..bcbe7bd3a 100644 --- a/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.html +++ b/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.html @@ -2,6 +2,7 @@ + diff --git a/src/Ombi/ClientApp/src/app/shared/role-directive/role-directive.ts b/src/Ombi/ClientApp/src/app/shared/role-directive/role-directive.ts new file mode 100644 index 000000000..3fa4cad56 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/shared/role-directive/role-directive.ts @@ -0,0 +1,36 @@ +import { Directive, Input, OnInit, TemplateRef, ViewContainerRef } from "@angular/core"; +import { AuthService } from "../../auth/auth.service"; + +@Directive({ + selector: '[permission]', +}) +export class RoleDirective implements OnInit { + private roleName: string; + + private isHidden = true; + + @Input() public set permission(val: string) { + if (val) { + this.roleName = val; + this.updateView(); + } + } + + public constructor(private templateRef: TemplateRef, private viewContainer: ViewContainerRef, private auth: AuthService) {} + + public ngOnInit(): void { + this.updateView(); + } + + private updateView(): void { + if (this.auth.hasRole(this.roleName) || this.auth.hasRole("admin")) { + if (this.isHidden) { + this.viewContainer.createEmbeddedView(this.templateRef); + this.isHidden = false; + } + } else { + this.viewContainer.clear(); + this.isHidden = true; + } + } +} diff --git a/src/Ombi/ClientApp/src/app/shared/role-directive/role.module.ts b/src/Ombi/ClientApp/src/app/shared/role-directive/role.module.ts new file mode 100644 index 000000000..15cea8923 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/shared/role-directive/role.module.ts @@ -0,0 +1,8 @@ +import { NgModule } from '@angular/core'; +import { RoleDirective } from './role-directive'; + +@NgModule({ + declarations: [RoleDirective], + exports: [RoleDirective], +}) +export class RoleModule {} diff --git a/src/Ombi/ClientApp/src/app/shared/shared.module.ts b/src/Ombi/ClientApp/src/app/shared/shared.module.ts index 01d70f1c6..f14eb43f2 100644 --- a/src/Ombi/ClientApp/src/app/shared/shared.module.ts +++ b/src/Ombi/ClientApp/src/app/shared/shared.module.ts @@ -43,6 +43,7 @@ import { TheMovieDbService } from "../services"; import { TranslateModule } from "@ngx-translate/core"; import { TruncateModule } from "@yellowspot/ng-truncate"; import { WatchProvidersSelectComponent } from "./components/watch-providers-select/watch-providers-select.component"; +import { RoleModule } from "./role-directive/role.module"; @NgModule({ declarations: [ @@ -56,6 +57,7 @@ import { WatchProvidersSelectComponent } from "./components/watch-providers-sele WatchProvidersSelectComponent, ], imports: [ + RoleModule, SidebarModule, ReactiveFormsModule, FormsModule, @@ -91,6 +93,7 @@ import { WatchProvidersSelectComponent } from "./components/watch-providers-sele PipeModule, ], exports: [ + RoleModule, TranslateModule, CommonModule, FormsModule, diff --git a/src/Ombi/ClientApp/src/app/state/customization/customization.state.ts b/src/Ombi/ClientApp/src/app/state/customization/customization.state.ts index 57b3f5908..5261183c3 100644 --- a/src/Ombi/ClientApp/src/app/state/customization/customization.state.ts +++ b/src/Ombi/ClientApp/src/app/state/customization/customization.state.ts @@ -6,7 +6,6 @@ import { ICustomizationSettings } from "../../interfaces"; import { Injectable } from "@angular/core"; import { Observable } from "rxjs"; import { SettingsService } from "../../services"; -import { produce } from 'immer'; import { tap } from "rxjs/operators"; @State({ diff --git a/src/Ombi/ClientApp/src/app/state/features/features-initializer.ts b/src/Ombi/ClientApp/src/app/state/features/features-initializer.ts new file mode 100644 index 000000000..2324259f3 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/state/features/features-initializer.ts @@ -0,0 +1,10 @@ +import { APP_INITIALIZER } from "@angular/core"; +import { FeaturesFacade } from "./features.facade"; +import { Observable } from "rxjs"; + +export const FEATURES_INITIALIZER = { + provide: APP_INITIALIZER, + useFactory: (featureFacade: FeaturesFacade) => (): Observable => featureFacade.loadFeatures(), + multi: true, + deps: [FeaturesFacade], +}; \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/state/features/features.actions.ts b/src/Ombi/ClientApp/src/app/state/features/features.actions.ts new file mode 100644 index 000000000..fcb517828 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/state/features/features.actions.ts @@ -0,0 +1,15 @@ +import { IFeatureEnablement } from "../../interfaces"; + +export class LoadFeatures { + public static readonly type = '[Features] LoadAll'; +} +export class EnableFeature { + public static readonly type = '[Features] Enable'; + + constructor(public feature: IFeatureEnablement) { } +} +export class DisableFeature { + public static readonly type = '[Features] Disable'; + + constructor(public feature: IFeatureEnablement) { } +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/state/features/features.facade.ts b/src/Ombi/ClientApp/src/app/state/features/features.facade.ts new file mode 100644 index 000000000..10e229eba --- /dev/null +++ b/src/Ombi/ClientApp/src/app/state/features/features.facade.ts @@ -0,0 +1,26 @@ +import { DisableFeature, EnableFeature, LoadFeatures } from "./features.actions"; + +import { FeaturesSelectors } from "./features.selectors"; +import { IFeatureEnablement } from "../../interfaces"; +import { Injectable } from "@angular/core"; +import { Observable } from "rxjs"; +import { Store } from "@ngxs/store"; + +@Injectable({ + providedIn: 'root', +}) +export class FeaturesFacade { + + public constructor(private store: Store) {} + + public features$ = (): Observable => this.store.select(FeaturesSelectors.features); + + public enable = (feature: IFeatureEnablement): Observable => this.store.dispatch(new EnableFeature(feature)); + + public disable = (feature: IFeatureEnablement): Observable => this.store.dispatch(new DisableFeature(feature)); + + public loadFeatures = (): Observable => this.store.dispatch(new LoadFeatures()); + + public is4kEnabled = (): boolean => this.store.selectSnapshot(FeaturesSelectors.is4kEnabled); + +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/state/features/features.selectors.ts b/src/Ombi/ClientApp/src/app/state/features/features.selectors.ts new file mode 100644 index 000000000..143dfb875 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/state/features/features.selectors.ts @@ -0,0 +1,18 @@ +import { ICustomizationSettings, IFeatureEnablement } from "../../interfaces"; + +import { FEATURES_STATE_TOKEN } from "./types"; +import { Selector } from "@ngxs/store"; + +export class FeaturesSelectors { + + @Selector([FEATURES_STATE_TOKEN]) + public static features(features: IFeatureEnablement[]): IFeatureEnablement[] { + return features; + } + + @Selector([FeaturesSelectors.features]) + public static is4kEnabled(features: IFeatureEnablement[]): boolean { + return features.filter(x => x.name === "Movie4KRequests")[0].enabled; + } + +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/state/features/features.state.ts b/src/Ombi/ClientApp/src/app/state/features/features.state.ts new file mode 100644 index 000000000..4e014db7c --- /dev/null +++ b/src/Ombi/ClientApp/src/app/state/features/features.state.ts @@ -0,0 +1,40 @@ +import { Action, State, StateContext } from "@ngxs/store"; +import { DisableFeature, EnableFeature, LoadFeatures } from "./features.actions"; + +import { FEATURES_STATE_TOKEN } from "./types"; +import { FeatureService } from "../../services/feature.service"; +import { IFeatureEnablement } from "../../interfaces"; +import { Injectable } from "@angular/core"; +import { Observable } from "rxjs"; +import { tap } from "rxjs/operators"; + +@State({ + name: FEATURES_STATE_TOKEN +}) +@Injectable() +export class FeatureState { + constructor(private featuresService: FeatureService) { } + + @Action(LoadFeatures) + public load({ setState }: StateContext): Observable { + return this.featuresService.getFeatures().pipe( + tap(features => + setState(features) + ) + ); + } + + @Action(EnableFeature) + public enable({ setState }: StateContext, { feature }: EnableFeature): Observable { + return this.featuresService.enable(feature).pipe( + tap((result) => setState(result)) + ); + } + + @Action(DisableFeature) + public disable({ setState }: StateContext, { feature }: DisableFeature): Observable { + return this.featuresService.disable(feature).pipe( + tap((result) => setState(result)) + ); + } +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/state/features/index.ts b/src/Ombi/ClientApp/src/app/state/features/index.ts new file mode 100644 index 000000000..e0fec5274 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/state/features/index.ts @@ -0,0 +1,4 @@ +export * from './features.state'; +export * from './features.actions'; +export * from './features.facade'; +export * from './features.selectors'; diff --git a/src/Ombi/ClientApp/src/app/state/features/types.ts b/src/Ombi/ClientApp/src/app/state/features/types.ts new file mode 100644 index 000000000..d8fdc09a1 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/state/features/types.ts @@ -0,0 +1,4 @@ +import { IFeatureEnablement } from "../../interfaces"; +import { StateToken } from "@ngxs/store"; + +export const FEATURES_STATE_TOKEN = new StateToken('featureEnablement'); \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.ts b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.ts index c772b86aa..bc6319198 100644 --- a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.ts +++ b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.ts @@ -6,6 +6,7 @@ import { IdentityService, MessageService, RadarrService, SettingsService, Sonarr import { Clipboard } from '@angular/cdk/clipboard'; import { CustomizationFacade } from "../state/customization"; import { Location } from "@angular/common"; +import { FeaturesFacade } from "../state/features/features.facade"; @Component({ templateUrl: "./usermanagement-user.component.html", @@ -36,7 +37,6 @@ export class UserManagementUserComponent implements OnInit { constructor(private identityService: IdentityService, private notificationService: MessageService, - private readonly settingsService: SettingsService, private router: Router, private route: ActivatedRoute, private sonarrService: SonarrService, @@ -44,23 +44,33 @@ export class UserManagementUserComponent implements OnInit { private clipboard: Clipboard, private location: Location, private customizationFacade: CustomizationFacade, + private featureFacade: FeaturesFacade, ) { this.route.params.subscribe((params: any) => { if(params.id) { this.userId = params.id; this.edit = true; - this.identityService.getUserById(this.userId).subscribe(x => { - this.user = x; - }); } }); } public ngOnInit() { + const is4KEnabled = this.featureFacade.is4kEnabled(); + this.identityService.getUserById(this.userId).subscribe(x => { + this.user = x; + if (!is4KEnabled) { + this.user.claims = this.user.claims.filter(x => x.value !== "Request4KMovie"); + } + }); this.requestLimitTypes = [RequestLimitType.Day, RequestLimitType.Week, RequestLimitType.Month]; this.identityService.getSupportedStreamingCountries().subscribe(x => this.countries = x); - this.identityService.getAllAvailableClaims().subscribe(x => this.availableClaims = x); + this.identityService.getAllAvailableClaims().subscribe(x => { + this.availableClaims = x; + if (!is4KEnabled) { + this.availableClaims = this.availableClaims.filter(y => y.value !== "Request4KMovie"); + } + }); if(this.edit) { this.identityService.getNotificationPreferencesForUser(this.userId).subscribe(x => this.notificationPreferences = x); } else { diff --git a/src/Ombi/Controllers/V1/IdentityController.cs b/src/Ombi/Controllers/V1/IdentityController.cs index fded4cacc..7ef7b8561 100644 --- a/src/Ombi/Controllers/V1/IdentityController.cs +++ b/src/Ombi/Controllers/V1/IdentityController.cs @@ -46,57 +46,36 @@ namespace Ombi.Controllers.V1 [ApiController] public class IdentityController : Controller { - public IdentityController(OmbiUserManager user, IMapper mapper, RoleManager rm, IEmailProvider prov, + public IdentityController(OmbiUserManager user, + RoleManager rm, + IEmailProvider prov, ISettingsService s, ISettingsService c, ISettingsService ombiSettings, IWelcomeEmail welcome, - IMovieRequestRepository m, - ITvRequestRepository t, ILogger l, IPlexApi plexApi, ISettingsService settings, - IRepository requestLog, - IRepository issues, - IRepository issueComments, - IRepository notificationRepository, - IRepository subscriptionRepository, ISettingsService umSettings, IRepository notificationPreferences, IRepository userProfiles, - IMusicRequestRepository musicRepo, - IMovieRequestEngine movieRequestEngine, - ITvRequestEngine tvRequestEngine, - IMusicRequestEngine musicEngine, IUserDeletionEngine deletionEngine, IRequestLimitService requestLimitService, ICacheService cacheService) { UserManager = user; - Mapper = mapper; RoleManager = rm; EmailProvider = prov; EmailSettings = s; CustomizationSettings = c; WelcomeEmail = welcome; - MovieRepo = m; - MusicRepo = musicRepo; - TvRepo = t; _log = l; _plexApi = plexApi; _plexSettings = settings; - _issuesRepository = issues; - _requestLogRepository = requestLog; - _issueCommentsRepository = issueComments; OmbiSettings = ombiSettings; - _requestSubscriptionRepository = subscriptionRepository; - _notificationRepository = notificationRepository; _userManagementSettings = umSettings; - TvRequestEngine = tvRequestEngine; - MovieRequestEngine = movieRequestEngine; _userNotificationPreferences = notificationPreferences; _userQualityProfiles = userProfiles; - MusicRequestEngine = musicEngine; _deletionEngine = deletionEngine; _requestLimitService = requestLimitService; _cacheService = cacheService; @@ -108,27 +87,15 @@ namespace Ombi.Controllers.V1 private readonly ICacheService _cacheService; private RoleManager RoleManager { get; } - private IMapper Mapper { get; } private IEmailProvider EmailProvider { get; } private ISettingsService EmailSettings { get; } private ISettingsService CustomizationSettings { get; } private readonly ISettingsService _userManagementSettings; private ISettingsService OmbiSettings { get; } private IWelcomeEmail WelcomeEmail { get; } - private IMovieRequestRepository MovieRepo { get; } - private ITvRequestRepository TvRepo { get; } - private IMovieRequestEngine MovieRequestEngine { get; } - private IMusicRequestEngine MusicRequestEngine { get; } - private ITvRequestEngine TvRequestEngine { get; } - private IMusicRequestRepository MusicRepo { get; } private readonly ILogger _log; private readonly IPlexApi _plexApi; private readonly ISettingsService _plexSettings; - private readonly IRepository _issuesRepository; - private readonly IRepository _issueCommentsRepository; - private readonly IRepository _requestLogRepository; - private readonly IRepository _notificationRepository; - private readonly IRepository _requestSubscriptionRepository; private readonly IRepository _userNotificationPreferences; private readonly IRepository _userQualityProfiles; @@ -258,6 +225,8 @@ namespace Ombi.Controllers.V1 await CreateRole(OmbiRoles.ReceivesNewsletter); await CreateRole(OmbiRoles.ManageOwnRequests); await CreateRole(OmbiRoles.EditCustomPage); + await CreateRole(OmbiRoles.EditCustomPage); + await CreateRole(OmbiRoles.Request4KMovie); } private async Task CreateRole(string role) diff --git a/src/Ombi/Controllers/V1/RequestController.cs b/src/Ombi/Controllers/V1/RequestController.cs index cc686b506..1220c3afc 100644 --- a/src/Ombi/Controllers/V1/RequestController.cs +++ b/src/Ombi/Controllers/V1/RequestController.cs @@ -165,7 +165,7 @@ namespace Ombi.Controllers.V1 [PowerUser] public async Task ApproveMovie([FromBody] MovieUpdateModel model) { - return await MovieRequestEngine.ApproveMovieById(model.Id); + return await MovieRequestEngine.ApproveMovieById(model.Id, model.Is4K); } /// @@ -177,7 +177,7 @@ namespace Ombi.Controllers.V1 [PowerUser] public async Task MarkMovieAvailable([FromBody] MovieUpdateModel model) { - return await MovieRequestEngine.MarkAvailable(model.Id); + return await MovieRequestEngine.MarkAvailable(model.Id, model.Is4K); } /// @@ -189,7 +189,7 @@ namespace Ombi.Controllers.V1 [PowerUser] public async Task MarkMovieUnAvailable([FromBody] MovieUpdateModel model) { - return await MovieRequestEngine.MarkUnavailable(model.Id); + return await MovieRequestEngine.MarkUnavailable(model.Id, model.Is4K); } /// @@ -201,7 +201,7 @@ namespace Ombi.Controllers.V1 [PowerUser] public async Task DenyMovie([FromBody] DenyMovieModel model) { - return await MovieRequestEngine.DenyMovieById(model.Id, model.Reason); + return await MovieRequestEngine.DenyMovieById(model.Id, model.Reason, model.Is4K); } /// @@ -403,7 +403,7 @@ namespace Ombi.Controllers.V1 [PowerUser] public async Task MarkTvAvailable([FromBody] TvUpdateModel model) { - return await TvRequestEngine.MarkAvailable(model.Id); + return await TvRequestEngine.MarkAvailable(model.Id, false); } /// @@ -415,7 +415,7 @@ namespace Ombi.Controllers.V1 [PowerUser] public async Task MarkTvUnAvailable([FromBody] TvUpdateModel model) { - return await TvRequestEngine.MarkUnavailable(model.Id); + return await TvRequestEngine.MarkUnavailable(model.Id, false); } /// diff --git a/src/Ombi/Controllers/V1/SettingsController.cs b/src/Ombi/Controllers/V1/SettingsController.cs index fcd4bb1a4..ff4dea913 100644 --- a/src/Ombi/Controllers/V1/SettingsController.cs +++ b/src/Ombi/Controllers/V1/SettingsController.cs @@ -403,9 +403,13 @@ namespace Ombi.Controllers.V1 /// /// [HttpGet("radarr")] - public async Task RadarrSettings() + public async Task RadarrSettings() { - return await Get(); + return new RadarrCombinedModel + { + Radarr = await Get(), + Radarr4K = await Get(), + }; } /// @@ -474,9 +478,11 @@ namespace Ombi.Controllers.V1 /// The settings. /// [HttpPost("radarr")] - public async Task RadarrSettings([FromBody]RadarrSettings settings) + public async Task RadarrSettings([FromBody]RadarrCombinedModel settings) { - var result = await Save(settings); + var radarrResult = await Save(settings.Radarr); + var radarr4kResult = await Save(settings.Radarr4K); + var result = radarr4kResult && radarrResult; if (result) { _cache.Remove(CacheKeys.RadarrRootProfiles); diff --git a/src/Ombi/Controllers/V2/FeaturesController.cs b/src/Ombi/Controllers/V2/FeaturesController.cs new file mode 100644 index 000000000..16c512b8c --- /dev/null +++ b/src/Ombi/Controllers/V2/FeaturesController.cs @@ -0,0 +1,88 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Ombi.Attributes; +using Ombi.Core.Settings; +using Ombi.Settings.Settings.Models; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Ombi.Controllers.V2 +{ + public class FeaturesController : V2Controller + { + private readonly ISettingsService _features; + + public FeaturesController(ISettingsService features) => _features = features; + + [HttpGet] + [AllowAnonymous] + public async Task> GetFeatures() + { + var features = await _features.GetSettingsAsync(); + return PopulateFeatures(features?.Features ?? new List()); + } + + [HttpPost("enable")] + [Admin] + public async Task> Enable([FromBody] FeatureEnablement feature) + { + var featureSettings = await _features.GetSettingsAsync(); + var features = PopulateFeatures(featureSettings?.Features ?? new List()); + var featureToUpdate = features.First(x => x.Name.Equals(feature.Name)); + featureToUpdate.Enabled = true; + + featureSettings.Features = features; + + await _features.SaveSettingsAsync(featureSettings); + + return PopulateFeatures(featureSettings?.Features ?? new List()); + } + + [HttpPost("disable")] + [Admin] + public async Task> Disable([FromBody] FeatureEnablement feature) + { + var featureSettings = await _features.GetSettingsAsync(); + var features = PopulateFeatures(featureSettings?.Features ?? new List()); + var featureToUpdate = features.First(x => x.Name.Equals(feature.Name)); + featureToUpdate.Enabled = false; + + featureSettings.Features = features; + + await _features.SaveSettingsAsync(featureSettings); + + return PopulateFeatures(featureSettings?.Features ?? new List()); + } + + + private List PopulateFeatures(List existingFeatures) + { + var supported = GetSupportedFeatures().ToList(); + if (supported.Count == existingFeatures.Count) + { + return existingFeatures; + } + var diff = supported.Except(existingFeatures.Select(x => x.Name)); + + foreach (var feature in diff) + { + existingFeatures.Add(new FeatureEnablement + { + Name = feature + }); + } + return existingFeatures; + } + + private IEnumerable GetSupportedFeatures() + { + FieldInfo[] fieldInfos = typeof(FeatureNames).GetFields(BindingFlags.Public | + BindingFlags.Static | BindingFlags.FlattenHierarchy); + + return fieldInfos.Where(fi => fi.IsLiteral && !fi.IsInitOnly && fi.FieldType == typeof(string)).Select(x => (string)x.GetValue(x)); + } + } +} diff --git a/src/Ombi/Controllers/V2/RequestsController.cs b/src/Ombi/Controllers/V2/RequestsController.cs index 9f26e95bf..45a060b15 100644 --- a/src/Ombi/Controllers/V2/RequestsController.cs +++ b/src/Ombi/Controllers/V2/RequestsController.cs @@ -203,15 +203,15 @@ namespace Ombi.Controllers.V2 } [PowerUser] - [HttpPost("reprocess/{type}/{requestId}")] - public async Task ReProcessRequest(RequestType type, int requestId) + [HttpPost("reprocess/{type}/{requestId}/{is4K}")] + public async Task ReProcessRequest(RequestType type, int requestId, bool? is4K) { switch (type) { case RequestType.TvShow: - return Ok(await _tvRequestEngine.ReProcessRequest(requestId, HttpContext.RequestAborted)); + return Ok(await _tvRequestEngine.ReProcessRequest(requestId, false, HttpContext.RequestAborted)); case RequestType.Movie: - return Ok(await _movieRequestEngine.ReProcessRequest(requestId, HttpContext.RequestAborted)); + return Ok(await _movieRequestEngine.ReProcessRequest(requestId, is4K ?? false, HttpContext.RequestAborted)); } return BadRequest(); diff --git a/src/Ombi/Models/MovieUpdateModel.cs b/src/Ombi/Models/MovieUpdateModel.cs index db52d4f2b..72638db98 100644 --- a/src/Ombi/Models/MovieUpdateModel.cs +++ b/src/Ombi/Models/MovieUpdateModel.cs @@ -29,5 +29,6 @@ namespace Ombi.Core.Models.Requests public class MovieUpdateModel { public int Id { get; set; } + public bool Is4K { get; set; } } } \ No newline at end of file diff --git a/src/Ombi/Ombi.csproj b/src/Ombi/Ombi.csproj index 1f10c1058..5f836c27f 100644 --- a/src/Ombi/Ombi.csproj +++ b/src/Ombi/Ombi.csproj @@ -95,6 +95,7 @@ + diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index a6d9cb25a..d0e282dce 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -14,7 +14,9 @@ "Common": { "ContinueButton": "Continue", "Available": "Available", + "Available4K": "Available 4K", "Approved": "Approved", + "Approve4K": "Approve 4K", "Pending": "Pending", "PartiallyAvailable": "Partially Available", "Monitored": "Monitored", @@ -24,8 +26,10 @@ "RequestDenied": "Request Denied", "NotRequested": "Not Requested", "Requested": "Requested", + "Requested4K": "Requested 4K", "Search":"Search", "Request": "Request", + "Request4K": "Request 4K", "Denied": "Denied", "Approve": "Approve", "PartlyAvailable": "Partly Available", @@ -161,9 +165,13 @@ "ChangeRootFolder": "Root Folder", "ChangeQualityProfile": "Quality Profile", "MarkUnavailable": "Mark Unavailable", + "MarkUnavailable4K": "Mark Unavailable 4K", "MarkAvailable": "Mark Available", + "MarkAvailable4K": "Mark Available 4K", "Remove": "Remove", "Deny": "Deny", + "Deny4K": "Deny 4K", + "Has4KRequest": "Has 4K Request", "DenyReason": "Deny Reason", "DeniedReason": "Denied Reason", "Season": "Season", @@ -199,6 +207,7 @@ "RequestPanel": { "Delete":"Delete Request", "Approve":"Approve Request", + "Approve4K":"Approve 4K Request", "ChangeAvailability":"Mark Available", "Deleted": "Successfully deleted selected items", "Approved": "Successfully approved selected items" @@ -295,6 +304,7 @@ }, "MediaDetails": { "Denied": "Denied", + "Denied4K": "Denied 4K", "Trailers": "Trailers", "RecommendationsTitle": "Recommendations", "SimilarTitle": "Similar", @@ -363,6 +373,7 @@ "RequestDate": "Request Date:", "DeniedReason": "Denied Reason:", "ReProcessRequest": "Re-Process Request", + "ReProcessRequest4K": "Re-Process 4K Request", "Music": { "Type": "Type:", "Country": "Country:",