diff --git a/CHANGELOG.md b/CHANGELOG.md index 084805f29..1f3cafffb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,66 @@ ### **New Features** +- Added automation tests for the voting feature. [TidusJar] + +- Update LidarrAvailabilityChecker.cs. [Jamie] + +- Update CHANGELOG.md. [Jamie] + - Changes language selector to always show native language name. [Victor Usoltsev] - Updated test dependancies. [TidusJar] +- Added in the external repo so we can rip out external stuff. [TidusJar] + - Added the ability to purge/remove issues. [TidusJar] ### **Fixes** +- New translations en.json (French) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- When a users requests content and the voting is enabled, the user who requested is an automatic +1 vote. [TidusJar] + +- Revert, no idea how this happened. [TidusJar] + +- Fixed the build. Thanks Matt! [TidusJar] + +- Fixes untickable mass email checkboxes in Safari. [Victor Usoltsev] + +- [ImgBot] optimizes images. [ImgBotApp] + +- Revert "Feature/purge issues" [Jamie] + +- Fixed the issue where user preferences was not being inported into some notifications. [TidusJar] + +- New role to enable users to remove their own requests. [Anojh] + - Users can now remove their own requests. [Anojh] - New translations en.json (Danish) [Jamie] @@ -60,6 +112,12 @@ - Date and times are now in the local users date time. [TidusJar] +- Fixed the migration. [TidusJar] + +- ExternalContext migrations. [TidusJar] + +- The settings have now been split out of the main db. [TidusJar] + - Search for the Lidarr Album when it's a new artist. [TidusJar] - The album in Lidarr does not need to be marked as monitored for us to pick up it's available. Fixes #2536. [Jamie] diff --git a/appveyor.yml b/appveyor.yml index 862993a21..99ec1e669 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,10 +3,14 @@ configuration: Release os: Visual Studio 2017 environment: nodejs_version: "9.8.0" + typescript_version: "3.0.1" install: # Get the latest stable version of Node.js or io.js - ps: Install-Product node $env:nodejs_version + + - cmd: set path=%programfiles(x86)%\\Microsoft SDKs\TypeScript\3.0;%path% + - cmd: tsc -v build_script: - ps: ./build.ps1 --settings_skipverification=true diff --git a/src/Ombi.Api.Notifications/OneSignalApi.cs b/src/Ombi.Api.Notifications/OneSignalApi.cs index 8d5fc04a9..ee5c7e44a 100644 --- a/src/Ombi.Api.Notifications/OneSignalApi.cs +++ b/src/Ombi.Api.Notifications/OneSignalApi.cs @@ -26,7 +26,7 @@ namespace Ombi.Api.Notifications { return null; } - var id = await _appConfig.Get(ConfigurationTypes.Notification); + var id = await _appConfig.GetAsync(ConfigurationTypes.Notification); var request = new Request(string.Empty, ApiUrl, HttpMethod.Post); var body = new OneSignalNotificationBody diff --git a/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs index 152a1d923..bb8b9e64d 100644 --- a/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs @@ -12,6 +12,7 @@ namespace Ombi.Core.Engine.Interfaces Task> SearchMovieRequest(string search); Task RemoveMovieRequest(int requestId); + Task RemoveAllMovieRequests(); Task UpdateMovieRequest(MovieRequests request); Task ApproveMovie(MovieRequests request); diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 9fd6033bf..16fd7667b 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -416,6 +416,12 @@ namespace Ombi.Core.Engine await MovieRepository.Delete(request); } + public async Task RemoveAllMovieRequests() + { + var request = MovieRepository.GetAll(); + await MovieRepository.DeleteRange(request); + } + public async Task UserHasRequest(string userId) { return await MovieRepository.GetAll().AnyAsync(x => x.RequestedUserId == userId); @@ -483,7 +489,7 @@ namespace Ombi.Core.Engine RequestType = RequestType.Movie, }); - return new RequestEngineResult {Result = true, Message = $"{movieName} has been successfully added!"}; + return new RequestEngineResult {Result = true, Message = $"{movieName} has been successfully added!", RequestId = model.Id}; } public async Task GetRemainingRequests(OmbiUser user) diff --git a/src/Ombi.Core/Engine/MusicRequestEngine.cs b/src/Ombi.Core/Engine/MusicRequestEngine.cs index 89d1a221e..185f86c37 100644 --- a/src/Ombi.Core/Engine/MusicRequestEngine.cs +++ b/src/Ombi.Core/Engine/MusicRequestEngine.cs @@ -495,7 +495,7 @@ namespace Ombi.Core.Engine RequestType = RequestType.Album, }); - return new RequestEngineResult { Result = true, Message = $"{model.Title} has been successfully added!" }; + return new RequestEngineResult { Result = true, Message = $"{model.Title} has been successfully added!", RequestId = model.Id }; } diff --git a/src/Ombi.Core/Engine/RequestEngineResult.cs b/src/Ombi.Core/Engine/RequestEngineResult.cs index 1dc78d4b4..08f6d5129 100644 --- a/src/Ombi.Core/Engine/RequestEngineResult.cs +++ b/src/Ombi.Core/Engine/RequestEngineResult.cs @@ -6,5 +6,6 @@ public string Message { get; set; } public bool IsError => !string.IsNullOrEmpty(ErrorMessage); public string ErrorMessage { get; set; } + public int RequestId { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 7994b23aa..f1a5b9880 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -604,15 +604,16 @@ namespace Ombi.Core.Engine var result = await TvSender.Send(model); if (result.Success) { - return new RequestEngineResult { Result = true }; + return new RequestEngineResult { Result = true, RequestId = model.Id}; } return new RequestEngineResult { - ErrorMessage = result.Message + ErrorMessage = result.Message, + RequestId = model.Id }; } - return new RequestEngineResult { Result = true }; + return new RequestEngineResult { Result = true, RequestId = model.Id }; } public async Task GetRemainingRequests(OmbiUser user) diff --git a/src/Ombi.Core/Engine/VoteEngine.cs b/src/Ombi.Core/Engine/VoteEngine.cs index 2ab35c2fa..7c4d48a6f 100644 --- a/src/Ombi.Core/Engine/VoteEngine.cs +++ b/src/Ombi.Core/Engine/VoteEngine.cs @@ -149,13 +149,17 @@ namespace Ombi.Core.Engine public async Task UpVote(int requestId, RequestType requestType) { + var voteSettings = await _voteSettings.GetSettingsAsync(); + if (!voteSettings.Enabled) + { + return new VoteEngineResult {Result = true}; + } // How many votes does this have?! var currentVotes = GetVotes(requestId, requestType); - var voteSettings = await _voteSettings.GetSettingsAsync(); - // Does this user have a downvote? If so we should revert it and make it an upvote var user = await GetUser(); + // Does this user have a downvote? If so we should revert it and make it an upvote var currentVote = await GetVoteForUser(requestId, user.Id); if (currentVote != null && currentVote.VoteType == VoteType.Upvote) { @@ -206,7 +210,7 @@ namespace Ombi.Core.Engine { return new VoteEngineResult { - ErrorMessage = "Voted succesfully but could not approve movie!" + ErrorMessage = "Voted succesfully but could not approve!" }; } @@ -218,6 +222,11 @@ namespace Ombi.Core.Engine public async Task DownVote(int requestId, RequestType requestType) { + var voteSettings = await _voteSettings.GetSettingsAsync(); + if (!voteSettings.Enabled) + { + return new VoteEngineResult { Result = true }; + } var user = await GetUser(); var currentVote = await GetVoteForUser(requestId, user.Id); if (currentVote != null && currentVote.VoteType == VoteType.Downvote) diff --git a/src/Ombi.Core/Rule/Rules/Request/SonarrCacheRequestRule.cs b/src/Ombi.Core/Rule/Rules/Request/SonarrCacheRequestRule.cs index 65750a64b..625407f3c 100644 --- a/src/Ombi.Core/Rule/Rules/Request/SonarrCacheRequestRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/SonarrCacheRequestRule.cs @@ -7,12 +7,12 @@ namespace Ombi.Core.Rule.Rules.Request { public class SonarrCacheRequestRule : BaseRequestRule, IRules { - public SonarrCacheRequestRule(IOmbiContext ctx) + public SonarrCacheRequestRule(IExternalContext ctx) { _ctx = ctx; } - private readonly IOmbiContext _ctx; + private readonly IExternalContext _ctx; public Task Execute(BaseRequest obj) { diff --git a/src/Ombi.Core/Rule/Rules/Search/SonarrCacheSearchRule.cs b/src/Ombi.Core/Rule/Rules/Search/SonarrCacheSearchRule.cs index f9c5cd09d..03bdbe091 100644 --- a/src/Ombi.Core/Rule/Rules/Search/SonarrCacheSearchRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/SonarrCacheSearchRule.cs @@ -34,12 +34,12 @@ namespace Ombi.Core.Rule.Rules.Search { public class SonarrCacheSearchRule : BaseSearchRule, IRules { - public SonarrCacheSearchRule(IOmbiContext ctx) + public SonarrCacheSearchRule(IExternalContext ctx) { _ctx = ctx; } - private readonly IOmbiContext _ctx; + private readonly IExternalContext _ctx; public Task Execute(SearchViewModel obj) { diff --git a/src/Ombi.Core/Rule/Rules/SonarrCacheRule.cs b/src/Ombi.Core/Rule/Rules/SonarrCacheRule.cs index c851afb4b..7eac05d56 100644 --- a/src/Ombi.Core/Rule/Rules/SonarrCacheRule.cs +++ b/src/Ombi.Core/Rule/Rules/SonarrCacheRule.cs @@ -10,12 +10,12 @@ namespace Ombi.Core.Rule.Rules { public class SonarrCacheRule { - public SonarrCacheRule(IOmbiContext ctx) + public SonarrCacheRule(IExternalContext ctx) { _ctx = ctx; } - private readonly IOmbiContext _ctx; + private readonly IExternalContext _ctx; public async Task Execute(BaseRequest obj) { diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index 30ccb6973..f5184493c 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -129,8 +129,12 @@ namespace Ombi.DependencyInjection public static void RegisterStore(this IServiceCollection services) { services.AddEntityFrameworkSqlite().AddDbContext(); + services.AddEntityFrameworkSqlite().AddDbContext(); + services.AddEntityFrameworkSqlite().AddDbContext(); services.AddScoped(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6 + services.AddScoped(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6 + services.AddScoped(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6 services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -145,6 +149,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(typeof(ISettingsService<>), typeof(SettingsService<>)); services.AddTransient(typeof(IRepository<>), typeof(Repository<>)); + services.AddTransient(typeof(IExternalRepository<>), typeof(ExternalRepository<>)); } public static void RegisterServices(this IServiceCollection services) { diff --git a/src/Ombi.Helpers/OmbiRoles.cs b/src/Ombi.Helpers/OmbiRoles.cs index 1d584d57f..e0cfc5398 100644 --- a/src/Ombi.Helpers/OmbiRoles.cs +++ b/src/Ombi.Helpers/OmbiRoles.cs @@ -14,5 +14,6 @@ public const string RequestMusic = nameof(RequestMusic); public const string Disabled = nameof(Disabled); public const string ReceivesNewsletter = nameof(ReceivesNewsletter); + public const string ManageOwnRequests = nameof(ManageOwnRequests); } } \ No newline at end of file diff --git a/src/Ombi.Notifications/BaseNotification.cs b/src/Ombi.Notifications/BaseNotification.cs index d351c8283..53d6d5d9d 100644 --- a/src/Ombi.Notifications/BaseNotification.cs +++ b/src/Ombi.Notifications/BaseNotification.cs @@ -170,6 +170,24 @@ namespace Ombi.Notifications.Interfaces { return new NotificationMessageContent { Disabled = true }; } + + if (model.UserId.IsNullOrEmpty()) + { + if (model.RequestType == RequestType.Movie) + { + model.UserId = MovieRequest.RequestedUserId; + } + + if (model.RequestType == RequestType.Album) + { + model.UserId = AlbumRequest.RequestedUserId; + } + + if (model.RequestType == RequestType.TvShow) + { + model.UserId = TvRequest.RequestedUserId; + } + } var parsed = Parse(model, template, agent); return parsed; @@ -184,7 +202,7 @@ namespace Ombi.Notifications.Interfaces protected UserNotificationPreferences GetUserPreference(string userId, NotificationAgent agent) { return UserNotificationPreferences.GetAll() - .FirstOrDefault(x => x.Enabled && x.Agent == agent && x.UserId == userId); + .FirstOrDefault(x => x.Agent == agent && x.UserId == userId); } private NotificationMessageContent Parse(NotificationOptions model, NotificationTemplates template, NotificationAgent agent) diff --git a/src/Ombi.Notifications/NotificationMessageCurlys.cs b/src/Ombi.Notifications/NotificationMessageCurlys.cs index 1b655b84d..b178f0545 100644 --- a/src/Ombi.Notifications/NotificationMessageCurlys.cs +++ b/src/Ombi.Notifications/NotificationMessageCurlys.cs @@ -19,7 +19,7 @@ namespace Ombi.Notifications LoadIssues(opts); if (pref != null) { - UserPreference = pref.Enabled ? pref.Value : string.Empty; + UserPreference = pref.Value; } string title; @@ -268,6 +268,7 @@ namespace Ombi.Notifications {nameof(IssueUser),IssueUser}, {nameof(UserName),UserName}, {nameof(Alias),Alias}, + {nameof(UserPreference),UserPreference}, }; } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Couchpotato/CouchPotatoSync.cs b/src/Ombi.Schedule/Jobs/Couchpotato/CouchPotatoSync.cs index 8606de371..5e9f13534 100644 --- a/src/Ombi.Schedule/Jobs/Couchpotato/CouchPotatoSync.cs +++ b/src/Ombi.Schedule/Jobs/Couchpotato/CouchPotatoSync.cs @@ -42,7 +42,7 @@ namespace Ombi.Schedule.Jobs.Couchpotato public class CouchPotatoSync : ICouchPotatoSync { public CouchPotatoSync(ISettingsService cpSettings, - ICouchPotatoApi api, ILogger log, IOmbiContext ctx) + ICouchPotatoApi api, ILogger log, IExternalContext ctx) { _settings = cpSettings; _api = api; @@ -54,7 +54,7 @@ namespace Ombi.Schedule.Jobs.Couchpotato private readonly ISettingsService _settings; private readonly ICouchPotatoApi _api; private readonly ILogger _log; - private readonly IOmbiContext _ctx; + private readonly IExternalContext _ctx; public async Task Start() { diff --git a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs index 2e32b6478..d19fe561a 100644 --- a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs +++ b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs @@ -20,7 +20,7 @@ namespace Ombi.Schedule.Jobs.Lidarr { public class LidarrAlbumSync : ILidarrAlbumSync { - public LidarrAlbumSync(ISettingsService lidarr, ILidarrApi lidarrApi, ILogger log, IOmbiContext ctx, + public LidarrAlbumSync(ISettingsService lidarr, ILidarrApi lidarrApi, ILogger log, IExternalContext ctx, IBackgroundJobClient job, ILidarrAvailabilityChecker availability) { _lidarrSettings = lidarr; @@ -35,7 +35,7 @@ namespace Ombi.Schedule.Jobs.Lidarr private readonly ISettingsService _lidarrSettings; private readonly ILidarrApi _lidarrApi; private readonly ILogger _logger; - private readonly IOmbiContext _ctx; + private readonly IExternalContext _ctx; private readonly IBackgroundJobClient _job; private readonly ILidarrAvailabilityChecker _availability; diff --git a/src/Ombi.Schedule/Jobs/Lidarr/LidarrArtistSync.cs b/src/Ombi.Schedule/Jobs/Lidarr/LidarrArtistSync.cs index ac6264e3d..4117ee44a 100644 --- a/src/Ombi.Schedule/Jobs/Lidarr/LidarrArtistSync.cs +++ b/src/Ombi.Schedule/Jobs/Lidarr/LidarrArtistSync.cs @@ -20,7 +20,7 @@ namespace Ombi.Schedule.Jobs.Lidarr { public class LidarrArtistSync : ILidarrArtistSync { - public LidarrArtistSync(ISettingsService lidarr, ILidarrApi lidarrApi, ILogger log, IOmbiContext ctx, + public LidarrArtistSync(ISettingsService lidarr, ILidarrApi lidarrApi, ILogger log, IExternalContext ctx, IBackgroundJobClient background, ILidarrAlbumSync album) { _lidarrSettings = lidarr; @@ -35,7 +35,7 @@ namespace Ombi.Schedule.Jobs.Lidarr private readonly ISettingsService _lidarrSettings; private readonly ILidarrApi _lidarrApi; private readonly ILogger _logger; - private readonly IOmbiContext _ctx; + private readonly IExternalContext _ctx; private readonly IBackgroundJobClient _job; private readonly ILidarrAlbumSync _albumSync; diff --git a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAvailabilityChecker.cs b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAvailabilityChecker.cs index 9db24784d..5708dad6c 100644 --- a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAvailabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAvailabilityChecker.cs @@ -70,4 +70,4 @@ namespace Ombi.Schedule.Jobs.Lidarr } } } -} \ No newline at end of file +} diff --git a/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs b/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs index 72f0ef6f5..5ec8978b6 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs @@ -33,7 +33,7 @@ namespace Ombi.Schedule.Jobs.Ombi public class OmbiAutomaticUpdater : IOmbiAutomaticUpdater { public OmbiAutomaticUpdater(ILogger log, IChangeLogProcessor service, - ISettingsService s, IProcessProvider proc, IRepository appConfig) + ISettingsService s, IProcessProvider proc, IApplicationConfigRepository appConfig) { Logger = log; Processor = service; @@ -48,7 +48,7 @@ namespace Ombi.Schedule.Jobs.Ombi private ISettingsService Settings { get; } private readonly IProcessProvider _processProvider; private static PerformContext Ctx { get; set; } - private readonly IRepository _appConfig; + private readonly IApplicationConfigRepository _appConfig; public string[] GetVersion() { @@ -252,9 +252,8 @@ namespace Ombi.Schedule.Jobs.Ombi private string GetArgs(UpdateSettings settings) { - var config = _appConfig.GetAll(); - var url = config.FirstOrDefault(x => x.Type == ConfigurationTypes.Url); - var storage = config.FirstOrDefault(x => x.Type == ConfigurationTypes.StoragePath); + var url = _appConfig.Get(ConfigurationTypes.Url); + var storage = _appConfig.Get(ConfigurationTypes.StoragePath); var currentLocation = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); var processName = (settings.ProcessName.HasValue() ? settings.ProcessName : "Ombi"); @@ -345,7 +344,6 @@ namespace Ombi.Schedule.Jobs.Ombi if (disposing) { - _appConfig?.Dispose(); Settings?.Dispose(); } _disposed = true; diff --git a/src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs b/src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs index 0aea8cdc2..5c954def8 100644 --- a/src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs +++ b/src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs @@ -16,7 +16,7 @@ namespace Ombi.Schedule.Jobs.Radarr { public class RadarrSync : IRadarrSync { - public RadarrSync(ISettingsService radarr, IRadarrApi radarrApi, ILogger log, IOmbiContext ctx) + public RadarrSync(ISettingsService radarr, IRadarrApi radarrApi, ILogger log, IExternalContext ctx) { RadarrSettings = radarr; RadarrApi = radarrApi; @@ -28,7 +28,7 @@ namespace Ombi.Schedule.Jobs.Radarr private ISettingsService RadarrSettings { get; } private IRadarrApi RadarrApi { get; } private ILogger Logger { get; } - private readonly IOmbiContext _ctx; + private readonly IExternalContext _ctx; private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1); diff --git a/src/Ombi.Schedule/Jobs/SickRage/SickRageSync.cs b/src/Ombi.Schedule/Jobs/SickRage/SickRageSync.cs index d2330197d..92e0c2d55 100644 --- a/src/Ombi.Schedule/Jobs/SickRage/SickRageSync.cs +++ b/src/Ombi.Schedule/Jobs/SickRage/SickRageSync.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -17,7 +16,7 @@ namespace Ombi.Schedule.Jobs.SickRage { public class SickRageSync : ISickRageSync { - public SickRageSync(ISettingsService s, ISickRageApi api, ILogger l, IOmbiContext ctx) + public SickRageSync(ISettingsService s, ISickRageApi api, ILogger l, IExternalContext ctx) { _settings = s; _api = api; @@ -29,7 +28,7 @@ namespace Ombi.Schedule.Jobs.SickRage private readonly ISettingsService _settings; private readonly ISickRageApi _api; private readonly ILogger _log; - private readonly IOmbiContext _ctx; + private readonly IExternalContext _ctx; public async Task Start() { diff --git a/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs b/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs index ec5502581..e4c00c726 100644 --- a/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs +++ b/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs @@ -19,7 +19,7 @@ namespace Ombi.Schedule.Jobs.Sonarr { public class SonarrSync : ISonarrSync { - public SonarrSync(ISettingsService s, ISonarrApi api, ILogger l, IOmbiContext ctx) + public SonarrSync(ISettingsService s, ISonarrApi api, ILogger l, IExternalContext ctx) { _settings = s; _api = api; @@ -31,7 +31,7 @@ namespace Ombi.Schedule.Jobs.Sonarr private readonly ISettingsService _settings; private readonly ISonarrApi _api; private readonly ILogger _log; - private readonly IOmbiContext _ctx; + private readonly IExternalContext _ctx; public async Task Start() { diff --git a/src/Ombi.Store/Context/ExternalContext.cs b/src/Ombi.Store/Context/ExternalContext.cs new file mode 100644 index 000000000..eb2be6450 --- /dev/null +++ b/src/Ombi.Store/Context/ExternalContext.cs @@ -0,0 +1,68 @@ +using System.IO; +using Microsoft.EntityFrameworkCore; +using Ombi.Helpers; +using Ombi.Store.Entities; + +namespace Ombi.Store.Context +{ + public sealed class ExternalContext : DbContext, IExternalContext + { + private static bool _created; + public ExternalContext() + { + if (_created) return; + + _created = true; + Database.Migrate(); + } + + public DbSet PlexServerContent { get; set; } + public DbSet PlexSeasonsContent { get; set; } + public DbSet PlexEpisode { get; set; } + public DbSet RadarrCache { get; set; } + public DbSet CouchPotatoCache { get; set; } + public DbSet EmbyContent { get; set; } + public DbSet EmbyEpisode { get; set; } + + public DbSet SonarrCache { get; set; } + public DbSet LidarrArtistCache { get; set; } + public DbSet LidarrAlbumCache { get; set; } + public DbSet SonarrEpisodeCache { get; set; } + public DbSet SickRageCache { get; set; } + public DbSet SickRageEpisodeCache { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + var i = StoragePathSingleton.Instance; + if (string.IsNullOrEmpty(i.StoragePath)) + { + i.StoragePath = string.Empty; + } + optionsBuilder.UseSqlite($"Data Source={Path.Combine(i.StoragePath, "OmbiExternal.db")}"); + } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity().HasMany(x => x.Episodes) + .WithOne(x => x.Series) + .HasPrincipalKey(x => x.Key) + .HasForeignKey(x => x.GrandparentKey); + + builder.Entity() + .HasOne(p => p.Series) + .WithMany(b => b.Episodes) + .HasPrincipalKey(x => x.EmbyId) + .HasForeignKey(p => p.ParentId); + + base.OnModelCreating(builder); + } + + + public void Seed() + { + // VACUUM; + Database.ExecuteSqlCommand("VACUUM;"); + SaveChanges(); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Context/IDbContext.cs b/src/Ombi.Store/Context/IDbContext.cs new file mode 100644 index 000000000..d84aaaa3d --- /dev/null +++ b/src/Ombi.Store/Context/IDbContext.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Ombi.Store.Context +{ + public interface IDbContext : IDisposable + { + EntityEntry Update(object entity); + EntityEntry Update(TEntity entity) where TEntity : class; + int SaveChanges(); + void Seed(); + Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); + DatabaseFacade Database { get; } + EntityEntry Entry(T entry) where T : class; + EntityEntry Attach(TEntity entity) where TEntity : class; + DbSet Set() where TEntity : class; + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Context/IExternalContext.cs b/src/Ombi.Store/Context/IExternalContext.cs new file mode 100644 index 000000000..3f5d79a79 --- /dev/null +++ b/src/Ombi.Store/Context/IExternalContext.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using Ombi.Store.Entities; + +namespace Ombi.Store.Context +{ + public interface IExternalContext : IDbContext + { + DbSet CouchPotatoCache { get; set; } + DbSet EmbyContent { get; set; } + DbSet EmbyEpisode { get; set; } + DbSet LidarrAlbumCache { get; set; } + DbSet LidarrArtistCache { get; set; } + DbSet PlexEpisode { get; set; } + DbSet PlexServerContent { get; set; } + DbSet RadarrCache { get; set; } + DbSet SickRageCache { get; set; } + DbSet SickRageEpisodeCache { get; set; } + DbSet SonarrCache { get; set; } + DbSet SonarrEpisodeCache { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Context/IOmbiContext.cs b/src/Ombi.Store/Context/IOmbiContext.cs index 77134e1a3..a7fb3b47d 100644 --- a/src/Ombi.Store/Context/IOmbiContext.cs +++ b/src/Ombi.Store/Context/IOmbiContext.cs @@ -9,20 +9,15 @@ using Ombi.Store.Entities.Requests; namespace Ombi.Store.Context { - public interface IOmbiContext : IDisposable + public interface IOmbiContext : IDbContext { - int SaveChanges(); - Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); + + //DbSet PlexServerContent { get; set; } + //DbSet PlexEpisode { get; set; } DbSet Settings { get; set; } - DbSet PlexServerContent { get; set; } - DbSet PlexEpisode { get; set; } - DbSet RadarrCache { get; set; } - DbSet EmbyContent { get; set; } - DbSet EmbyEpisode { get; set; } - DatabaseFacade Database { get; } - EntityEntry Entry(T entry) where T : class; - EntityEntry Attach(TEntity entity) where TEntity : class; - DbSet Set() where TEntity : class; + //DbSet RadarrCache { get; set; } + //DbSet EmbyContent { get; set; } + //DbSet EmbyEpisode { get; set; } DbSet NotificationTemplates { get; set; } DbSet ApplicationConfigurations { get; set; } DbSet Votes { get; set; } @@ -36,14 +31,12 @@ namespace Ombi.Store.Context DbSet IssueCategories { get; set; } DbSet Tokens { get; set; } DbSet SonarrCache { get; set; } - DbSet SonarrEpisodeCache { get; set; } - EntityEntry Update(object entity); - EntityEntry Update(TEntity entity) where TEntity : class; - DbSet CouchPotatoCache { get; set; } - DbSet SickRageCache { get; set; } - DbSet LidarrArtistCache { get; set; } - DbSet LidarrAlbumCache { get; set; } - DbSet SickRageEpisodeCache { get; set; } + //DbSet SonarrEpisodeCache { get; set; } + //DbSet CouchPotatoCache { get; set; } + //DbSet SickRageCache { get; set; } + //DbSet LidarrArtistCache { get; set; } + //DbSet LidarrAlbumCache { get; set; } + //DbSet SickRageEpisodeCache { get; set; } DbSet RequestLogs { get; set; } DbSet RecentlyAddedLogs { get; set; } DbSet RequestSubscription { get; set; } diff --git a/src/Ombi.Store/Context/ISettingsContext.cs b/src/Ombi.Store/Context/ISettingsContext.cs new file mode 100644 index 000000000..3c209c68a --- /dev/null +++ b/src/Ombi.Store/Context/ISettingsContext.cs @@ -0,0 +1,11 @@ +using Microsoft.EntityFrameworkCore; +using Ombi.Store.Entities; + +namespace Ombi.Store.Context +{ + public interface ISettingsContext : IDbContext + { + DbSet ApplicationConfigurations { get; set; } + DbSet Settings { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Context/OmbiContext.cs b/src/Ombi.Store/Context/OmbiContext.cs index 66631dfc6..16f26c65a 100644 --- a/src/Ombi.Store/Context/OmbiContext.cs +++ b/src/Ombi.Store/Context/OmbiContext.cs @@ -22,9 +22,11 @@ namespace Ombi.Store.Context } public DbSet NotificationTemplates { get; set; } - public DbSet Settings { get; set; } + public DbSet ApplicationConfigurations { get; set; } public DbSet PlexServerContent { get; set; } + public DbSet PlexSeasonsContent { get; set; } public DbSet PlexEpisode { get; set; } + public DbSet Settings { get; set; } public DbSet RadarrCache { get; set; } public DbSet CouchPotatoCache { get; set; } public DbSet EmbyContent { get; set; } @@ -54,7 +56,6 @@ namespace Ombi.Store.Context public DbSet RequestSubscription { get; set; } public DbSet UserNotificationPreferences { get; set; } public DbSet UserQualityProfileses { get; set; } - public DbSet ApplicationConfigurations { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { @@ -85,39 +86,6 @@ namespace Ombi.Store.Context public void Seed() { - - // Add the tokens - var fanArt = ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.FanartTv); - if (fanArt == null) - { - ApplicationConfigurations.Add(new ApplicationConfiguration - { - Type = ConfigurationTypes.FanartTv, - Value = "4b6d983efa54d8f45c68432521335f15" - }); - SaveChanges(); - } - var movieDb = ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.FanartTv); - if (movieDb == null) - { - ApplicationConfigurations.Add(new ApplicationConfiguration - { - Type = ConfigurationTypes.TheMovieDb, - Value = "b8eabaf5608b88d0298aa189dd90bf00" - }); - SaveChanges(); - } - var notification = ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.Notification); - if (notification == null) - { - ApplicationConfigurations.Add(new ApplicationConfiguration - { - Type = ConfigurationTypes.Notification, - Value = "4f0260c4-9c3d-41ab-8d68-27cb5a593f0e" - }); - SaveChanges(); - } - // VACUUM; Database.ExecuteSqlCommand("VACUUM;"); @@ -145,6 +113,16 @@ namespace Ombi.Store.Context SaveChanges(); } + var manageOwnRequestsRole = Roles.Where(x => x.Name == OmbiRoles.ManageOwnRequests); + if (!manageOwnRequestsRole.Any()) + { + Roles.Add(new IdentityRole(OmbiRoles.ManageOwnRequests) + { + NormalizedName = OmbiRoles.ManageOwnRequests.ToUpper() + }); + SaveChanges(); + } + // Make sure we have the API User var apiUserExists = Users.Any(x => x.UserName.Equals("Api", StringComparison.CurrentCultureIgnoreCase)); if (!apiUserExists) diff --git a/src/Ombi.Store/Context/SettingsContext.cs b/src/Ombi.Store/Context/SettingsContext.cs new file mode 100644 index 000000000..af5c91d20 --- /dev/null +++ b/src/Ombi.Store/Context/SettingsContext.cs @@ -0,0 +1,70 @@ +using System.IO; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Ombi.Helpers; +using Ombi.Store.Entities; + +namespace Ombi.Store.Context +{ + public sealed class SettingsContext : DbContext, ISettingsContext + { + private static bool _created; + public SettingsContext() + { + if (_created) return; + + _created = true; + Database.Migrate(); + } + + public DbSet Settings { get; set; } + public DbSet ApplicationConfigurations { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + var i = StoragePathSingleton.Instance; + if (string.IsNullOrEmpty(i.StoragePath)) + { + i.StoragePath = string.Empty; + } + optionsBuilder.UseSqlite($"Data Source={Path.Combine(i.StoragePath, "OmbiSettings.db")}"); + } + + public void Seed() + { + // Add the tokens + var fanArt = ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.FanartTv); + if (fanArt == null) + { + ApplicationConfigurations.Add(new ApplicationConfiguration + { + Type = ConfigurationTypes.FanartTv, + Value = "4b6d983efa54d8f45c68432521335f15" + }); + SaveChanges(); + } + var movieDb = ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.FanartTv); + if (movieDb == null) + { + ApplicationConfigurations.Add(new ApplicationConfiguration + { + Type = ConfigurationTypes.TheMovieDb, + Value = "b8eabaf5608b88d0298aa189dd90bf00" + }); + SaveChanges(); + } + var notification = ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.Notification); + if (notification == null) + { + ApplicationConfigurations.Add(new ApplicationConfiguration + { + Type = ConfigurationTypes.Notification, + Value = "4f0260c4-9c3d-41ab-8d68-27cb5a593f0e" + }); + SaveChanges(); + } + + SaveChanges(); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Entities/PlexServerContent.cs b/src/Ombi.Store/Entities/PlexServerContent.cs index 14028cb57..17d8ceffb 100644 --- a/src/Ombi.Store/Entities/PlexServerContent.cs +++ b/src/Ombi.Store/Entities/PlexServerContent.cs @@ -42,13 +42,9 @@ namespace Ombi.Store.Entities public PlexMediaTypeEntity Type { get; set; } public string Url { get; set; } - - /// - /// Only used for TV Shows - /// - public virtual ICollection Seasons { get; set; } public ICollection Episodes { get; set; } + public ICollection Seasons { get; set; } /// /// Plex's internal ID for this item diff --git a/src/Ombi.Store/Migrations/External/20181004134907_Inital.Designer.cs b/src/Ombi.Store/Migrations/External/20181004134907_Inital.Designer.cs new file mode 100644 index 000000000..776d3e082 --- /dev/null +++ b/src/Ombi.Store/Migrations/External/20181004134907_Inital.Designer.cs @@ -0,0 +1,312 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations.External +{ + [DbContext(typeof(ExternalContext))] + [Migration("20181004134907_Inital")] + partial class Inital + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + 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"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/External/20181004134907_Inital.cs b/src/Ombi.Store/Migrations/External/20181004134907_Inital.cs new file mode 100644 index 000000000..bc4dc509e --- /dev/null +++ b/src/Ombi.Store/Migrations/External/20181004134907_Inital.cs @@ -0,0 +1,308 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations.External +{ + public partial class Inital : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "CouchPotatoCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + TheMovieDbId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CouchPotatoCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "EmbyContent", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Title = table.Column(nullable: true), + ProviderId = table.Column(nullable: true), + EmbyId = table.Column(nullable: false), + Type = table.Column(nullable: false), + AddedAt = table.Column(nullable: false), + ImdbId = table.Column(nullable: true), + TheMovieDbId = table.Column(nullable: true), + TvDbId = table.Column(nullable: true), + Url = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_EmbyContent", x => x.Id); + table.UniqueConstraint("AK_EmbyContent_EmbyId", x => x.EmbyId); + }); + + migrationBuilder.CreateTable( + name: "LidarrAlbumCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ArtistId = table.Column(nullable: false), + ForeignAlbumId = table.Column(nullable: true), + TrackCount = table.Column(nullable: false), + ReleaseDate = table.Column(nullable: false), + Monitored = table.Column(nullable: false), + Title = table.Column(nullable: true), + PercentOfTracks = table.Column(nullable: false), + AddedAt = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LidarrAlbumCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "LidarrArtistCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ArtistId = table.Column(nullable: false), + ArtistName = table.Column(nullable: true), + ForeignArtistId = table.Column(nullable: true), + Monitored = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LidarrArtistCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PlexServerContent", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Title = table.Column(nullable: true), + ReleaseYear = table.Column(nullable: true), + ImdbId = table.Column(nullable: true), + TvDbId = table.Column(nullable: true), + TheMovieDbId = table.Column(nullable: true), + Type = table.Column(nullable: false), + Url = table.Column(nullable: true), + Key = table.Column(nullable: false), + AddedAt = table.Column(nullable: false), + Quality = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PlexServerContent", x => x.Id); + table.UniqueConstraint("AK_PlexServerContent_Key", x => x.Key); + }); + + migrationBuilder.CreateTable( + name: "RadarrCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + TheMovieDbId = table.Column(nullable: false), + HasFile = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RadarrCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SickRageCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + TvDbId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SickRageCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SickRageEpisodeCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + SeasonNumber = table.Column(nullable: false), + EpisodeNumber = table.Column(nullable: false), + TvDbId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SickRageEpisodeCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SonarrCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + TvDbId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SonarrCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SonarrEpisodeCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + SeasonNumber = table.Column(nullable: false), + EpisodeNumber = table.Column(nullable: false), + TvDbId = table.Column(nullable: false), + HasFile = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SonarrEpisodeCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "EmbyEpisode", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Title = table.Column(nullable: true), + EmbyId = table.Column(nullable: true), + EpisodeNumber = table.Column(nullable: false), + SeasonNumber = table.Column(nullable: false), + ParentId = table.Column(nullable: true), + ProviderId = table.Column(nullable: true), + AddedAt = table.Column(nullable: false), + TvDbId = table.Column(nullable: true), + ImdbId = table.Column(nullable: true), + TheMovieDbId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_EmbyEpisode", x => x.Id); + table.ForeignKey( + name: "FK_EmbyEpisode_EmbyContent_ParentId", + column: x => x.ParentId, + principalTable: "EmbyContent", + principalColumn: "EmbyId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "PlexEpisode", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + EpisodeNumber = table.Column(nullable: false), + SeasonNumber = table.Column(nullable: false), + Key = table.Column(nullable: false), + Title = table.Column(nullable: true), + ParentKey = table.Column(nullable: false), + GrandparentKey = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlexEpisode", x => x.Id); + table.ForeignKey( + name: "FK_PlexEpisode_PlexServerContent_GrandparentKey", + column: x => x.GrandparentKey, + principalTable: "PlexServerContent", + principalColumn: "Key", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PlexSeasonsContent", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlexContentId = table.Column(nullable: false), + SeasonNumber = table.Column(nullable: false), + SeasonKey = table.Column(nullable: false), + ParentKey = table.Column(nullable: false), + PlexServerContentId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PlexSeasonsContent", x => x.Id); + table.ForeignKey( + name: "FK_PlexSeasonsContent_PlexServerContent_PlexServerContentId", + column: x => x.PlexServerContentId, + principalTable: "PlexServerContent", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_EmbyEpisode_ParentId", + table: "EmbyEpisode", + column: "ParentId"); + + migrationBuilder.CreateIndex( + name: "IX_PlexEpisode_GrandparentKey", + table: "PlexEpisode", + column: "GrandparentKey"); + + migrationBuilder.CreateIndex( + name: "IX_PlexSeasonsContent_PlexServerContentId", + table: "PlexSeasonsContent", + column: "PlexServerContentId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CouchPotatoCache"); + + migrationBuilder.DropTable( + name: "EmbyEpisode"); + + migrationBuilder.DropTable( + name: "LidarrAlbumCache"); + + migrationBuilder.DropTable( + name: "LidarrArtistCache"); + + migrationBuilder.DropTable( + name: "PlexEpisode"); + + migrationBuilder.DropTable( + name: "PlexSeasonsContent"); + + migrationBuilder.DropTable( + name: "RadarrCache"); + + migrationBuilder.DropTable( + name: "SickRageCache"); + + migrationBuilder.DropTable( + name: "SickRageEpisodeCache"); + + migrationBuilder.DropTable( + name: "SonarrCache"); + + migrationBuilder.DropTable( + name: "SonarrEpisodeCache"); + + migrationBuilder.DropTable( + name: "EmbyContent"); + + migrationBuilder.DropTable( + name: "PlexServerContent"); + } + } +} diff --git a/src/Ombi.Store/Migrations/External/ExternalContextModelSnapshot.cs b/src/Ombi.Store/Migrations/External/ExternalContextModelSnapshot.cs new file mode 100644 index 000000000..4e97b5514 --- /dev/null +++ b/src/Ombi.Store/Migrations/External/ExternalContextModelSnapshot.cs @@ -0,0 +1,310 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations.External +{ + [DbContext(typeof(ExternalContext))] + partial class ExternalContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + 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"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/Settings/20181004132516_Inital.Designer.cs b/src/Ombi.Store/Migrations/Settings/20181004132516_Inital.Designer.cs new file mode 100644 index 000000000..60b9f9adc --- /dev/null +++ b/src/Ombi.Store/Migrations/Settings/20181004132516_Inital.Designer.cs @@ -0,0 +1,50 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations.Settings +{ + [DbContext(typeof(SettingsContext))] + [Migration("20181004132516_Inital")] + partial class Inital + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/Settings/20181004132516_Inital.cs b/src/Ombi.Store/Migrations/Settings/20181004132516_Inital.cs new file mode 100644 index 000000000..956e0a14a --- /dev/null +++ b/src/Ombi.Store/Migrations/Settings/20181004132516_Inital.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations.Settings +{ + public partial class Inital : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ApplicationConfiguration", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Type = table.Column(nullable: false), + Value = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ApplicationConfiguration", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "GlobalSettings", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Content = table.Column(nullable: true), + SettingsName = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_GlobalSettings", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ApplicationConfiguration"); + + migrationBuilder.DropTable( + name: "GlobalSettings"); + } + } +} diff --git a/src/Ombi.Store/Migrations/Settings/SettingsContextModelSnapshot.cs b/src/Ombi.Store/Migrations/Settings/SettingsContextModelSnapshot.cs new file mode 100644 index 000000000..2f072d3dd --- /dev/null +++ b/src/Ombi.Store/Migrations/Settings/SettingsContextModelSnapshot.cs @@ -0,0 +1,48 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations.Settings +{ + [DbContext(typeof(SettingsContext))] + partial class SettingsContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Repository/ApplicationConfigRepository.cs b/src/Ombi.Store/Repository/ApplicationConfigRepository.cs index 3b4476454..31ec3313c 100644 --- a/src/Ombi.Store/Repository/ApplicationConfigRepository.cs +++ b/src/Ombi.Store/Repository/ApplicationConfigRepository.cs @@ -8,16 +8,20 @@ namespace Ombi.Store.Repository { public class ApplicationConfigRepository : IApplicationConfigRepository { - public ApplicationConfigRepository(IOmbiContext ctx) + public ApplicationConfigRepository(ISettingsContext ctx) { Ctx = ctx; } - private IOmbiContext Ctx { get; } + private ISettingsContext Ctx { get; } - public async Task Get(ConfigurationTypes type) + public Task GetAsync(ConfigurationTypes type) { - return await Ctx.ApplicationConfigurations.FirstOrDefaultAsync(x => x.Type == type); + return Ctx.ApplicationConfigurations.FirstOrDefaultAsync(x => x.Type == type); + } + public ApplicationConfiguration Get(ConfigurationTypes type) + { + return Ctx.ApplicationConfigurations.FirstOrDefault(x => x.Type == type); } } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/BaseRepository.cs b/src/Ombi.Store/Repository/BaseRepository.cs new file mode 100644 index 000000000..1679035dd --- /dev/null +++ b/src/Ombi.Store/Repository/BaseRepository.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Query; +using Ombi.Store.Context; +using Ombi.Store.Entities; + +namespace Ombi.Store.Repository +{ + public class BaseRepository : IRepository where T : Entity where U : IDbContext + { + public BaseRepository(U ctx) + { + _ctx = ctx; + _db = _ctx.Set(); + } + public DbSet _db { get; } + private readonly U _ctx; + + public async Task Find(object key) + { + return await _db.FindAsync(key); + } + + public IQueryable GetAll() + { + return _db.AsQueryable(); + } + + public async Task FirstOrDefaultAsync(Expression> predicate) + { + return await _db.FirstOrDefaultAsync(predicate); + } + + public async Task AddRange(IEnumerable content, bool save = true) + { + _db.AddRange(content); + if (save) + { + await _ctx.SaveChangesAsync(); + } + } + + public async Task Add(T content) + { + await _db.AddAsync(content); + await _ctx.SaveChangesAsync(); + return content; + } + + public async Task Delete(T request) + { + _db.Remove(request); + await _ctx.SaveChangesAsync(); + } + + public async Task DeleteRange(IEnumerable req) + { + _db.RemoveRange(req); + await _ctx.SaveChangesAsync(); + } + + public async Task SaveChangesAsync() + { + return await _ctx.SaveChangesAsync(); + } + + public IIncludableQueryable Include( + IQueryable source, Expression> navigationPropertyPath) + where TEntity : class + { + return source.Include(navigationPropertyPath); + } + + public async Task ExecuteSql(string sql) + { + await _ctx.Database.ExecuteSqlCommandAsync(sql); + } + + + private bool _disposed; + // Protected implementation of Dispose pattern. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + _ctx?.Dispose(); + } + + _disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Repository/EmbyContentRepository.cs b/src/Ombi.Store/Repository/EmbyContentRepository.cs index c4377f929..4d32e8da2 100644 --- a/src/Ombi.Store/Repository/EmbyContentRepository.cs +++ b/src/Ombi.Store/Repository/EmbyContentRepository.cs @@ -35,15 +35,15 @@ using Ombi.Store.Entities; namespace Ombi.Store.Repository { - public class EmbyContentRepository : Repository, IEmbyContentRepository + public class EmbyContentRepository : ExternalRepository, IEmbyContentRepository { - public EmbyContentRepository(IOmbiContext db):base(db) + public EmbyContentRepository(IExternalContext db):base(db) { Db = db; } - private IOmbiContext Db { get; } + private IExternalContext Db { get; } public async Task GetByImdbId(string imdbid) diff --git a/src/Ombi.Store/Repository/ExternalRepository.cs b/src/Ombi.Store/Repository/ExternalRepository.cs new file mode 100644 index 000000000..d7494afd4 --- /dev/null +++ b/src/Ombi.Store/Repository/ExternalRepository.cs @@ -0,0 +1,12 @@ +using Ombi.Store.Context; +using Ombi.Store.Entities; + +namespace Ombi.Store.Repository +{ + public class ExternalRepository : BaseRepository, IExternalRepository where T : Entity + { + public ExternalRepository(IExternalContext ctx) : base(ctx) + { + } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Repository/IApplicationConfigRepository.cs b/src/Ombi.Store/Repository/IApplicationConfigRepository.cs index 9e35bde7e..6aa76e357 100644 --- a/src/Ombi.Store/Repository/IApplicationConfigRepository.cs +++ b/src/Ombi.Store/Repository/IApplicationConfigRepository.cs @@ -5,6 +5,7 @@ namespace Ombi.Store.Repository { public interface IApplicationConfigRepository { - Task Get(ConfigurationTypes type); + Task GetAsync(ConfigurationTypes type); + ApplicationConfiguration Get(ConfigurationTypes type); } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/IExternalRepository.cs b/src/Ombi.Store/Repository/IExternalRepository.cs new file mode 100644 index 000000000..de8b6db67 --- /dev/null +++ b/src/Ombi.Store/Repository/IExternalRepository.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Query; +using Ombi.Store.Entities; + +namespace Ombi.Store.Repository +{ + public interface IExternalRepository : IDisposable where T : Entity + { + Task Find(object key); + IQueryable GetAll(); + Task FirstOrDefaultAsync(Expression> predicate); + Task AddRange(IEnumerable content, bool save = true); + Task Add(T content); + Task DeleteRange(IEnumerable req); + Task Delete(T request); + Task SaveChangesAsync(); + + IIncludableQueryable Include( + IQueryable source, Expression> navigationPropertyPath) + where TEntity : class; + + Task ExecuteSql(string sql); + DbSet _db { get; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Repository/IPlexContentRepository.cs b/src/Ombi.Store/Repository/IPlexContentRepository.cs index 381a89fa3..7bce2e75a 100644 --- a/src/Ombi.Store/Repository/IPlexContentRepository.cs +++ b/src/Ombi.Store/Repository/IPlexContentRepository.cs @@ -7,7 +7,7 @@ using Ombi.Store.Entities; namespace Ombi.Store.Repository { - public interface IPlexContentRepository : IRepository + public interface IPlexContentRepository : IExternalRepository { Task ContentExists(string providerId); Task Get(string providerId); diff --git a/src/Ombi.Store/Repository/PlexContentRepository.cs b/src/Ombi.Store/Repository/PlexContentRepository.cs index e452eeb7d..2c9c28d09 100644 --- a/src/Ombi.Store/Repository/PlexContentRepository.cs +++ b/src/Ombi.Store/Repository/PlexContentRepository.cs @@ -36,15 +36,15 @@ using Ombi.Store.Entities; namespace Ombi.Store.Repository { - public class PlexServerContentRepository : Repository, IPlexContentRepository + public class PlexServerContentRepository : ExternalRepository, IPlexContentRepository { - public PlexServerContentRepository(IOmbiContext db) : base(db) + public PlexServerContentRepository(IExternalContext db) : base(db) { Db = db; } - private IOmbiContext Db { get; } + private IExternalContext Db { get; } public async Task ContentExists(string providerId) diff --git a/src/Ombi.Store/Repository/Repository.cs b/src/Ombi.Store/Repository/Repository.cs index 8c07c2371..9d49ded58 100644 --- a/src/Ombi.Store/Repository/Repository.cs +++ b/src/Ombi.Store/Repository/Repository.cs @@ -1,105 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Query; -using Ombi.Store.Context; +using Ombi.Store.Context; using Ombi.Store.Entities; namespace Ombi.Store.Repository { - public class Repository : IRepository where T : Entity + public class Repository : BaseRepository, IRepository where T : Entity { - public Repository(IOmbiContext ctx) + public Repository(IOmbiContext ctx) : base(ctx) { - _ctx = ctx; - _db = _ctx.Set(); - } - public DbSet _db { get; } - private readonly IOmbiContext _ctx; - - public async Task Find(object key) - { - return await _db.FindAsync(key); - } - - public IQueryable GetAll() - { - return _db.AsQueryable(); - } - - public async Task FirstOrDefaultAsync(Expression> predicate) - { - return await _db.FirstOrDefaultAsync(predicate); - } - - public async Task AddRange(IEnumerable content, bool save = true) - { - _db.AddRange(content); - if (save) - { - await _ctx.SaveChangesAsync(); - } - } - - public async Task Add(T content) - { - await _db.AddAsync(content); - await _ctx.SaveChangesAsync(); - return content; - } - - public async Task Delete(T request) - { - _db.Remove(request); - await _ctx.SaveChangesAsync(); - } - - public async Task DeleteRange(IEnumerable req) - { - _db.RemoveRange(req); - await _ctx.SaveChangesAsync(); - } - - public async Task SaveChangesAsync() - { - return await _ctx.SaveChangesAsync(); - } - - public IIncludableQueryable Include( - IQueryable source, Expression> navigationPropertyPath) - where TEntity : class - { - return source.Include(navigationPropertyPath); - } - - public async Task ExecuteSql(string sql) - { - await _ctx.Database.ExecuteSqlCommandAsync(sql); - } - - - private bool _disposed; - // Protected implementation of Dispose pattern. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - return; - - if (disposing) - { - _ctx?.Dispose(); - } - - _disposed = true; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); } } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/SettingsJsonRepository.cs b/src/Ombi.Store/Repository/SettingsJsonRepository.cs index 248413ccc..66cf57b18 100644 --- a/src/Ombi.Store/Repository/SettingsJsonRepository.cs +++ b/src/Ombi.Store/Repository/SettingsJsonRepository.cs @@ -12,13 +12,13 @@ namespace Ombi.Store.Repository { public class SettingsJsonRepository : ISettingsRepository { - public SettingsJsonRepository(IOmbiContext ctx, ICacheService mem) + public SettingsJsonRepository(ISettingsContext ctx, ICacheService mem) { Db = ctx; _cache = mem; } - private IOmbiContext Db { get; } + private ISettingsContext Db { get; } private readonly ICacheService _cache; public GlobalSettings Insert(GlobalSettings entity) diff --git a/src/Ombi/Attributes/UserAttribute.cs b/src/Ombi/Attributes/UserAttribute.cs new file mode 100644 index 000000000..3ab4cef49 --- /dev/null +++ b/src/Ombi/Attributes/UserAttribute.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Authorization; +using Ombi.Helpers; + + +namespace Ombi.Attributes +{ + public class UserAttribute : AuthorizeAttribute + { + public UserAttribute() + { + Roles = "ManageOwnRequests"; + } + } +} diff --git a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html index 28734f5a3..c4c903da2 100644 --- a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html +++ b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html @@ -186,7 +186,7 @@
-
+ diff --git a/src/Ombi/ClientApp/app/settings/massemail/massemail.component.html b/src/Ombi/ClientApp/app/settings/massemail/massemail.component.html index e66d83a18..5c51c68ca 100644 --- a/src/Ombi/ClientApp/app/settings/massemail/massemail.component.html +++ b/src/Ombi/ClientApp/app/settings/massemail/massemail.component.html @@ -39,7 +39,7 @@
- +
diff --git a/src/Ombi/ClientApp/app/settings/massemail/massemail.component.ts b/src/Ombi/ClientApp/app/settings/massemail/massemail.component.ts index a80f7adfe..91693103f 100644 --- a/src/Ombi/ClientApp/app/settings/massemail/massemail.component.ts +++ b/src/Ombi/ClientApp/app/settings/massemail/massemail.component.ts @@ -38,10 +38,6 @@ export class MassEmailComponent implements OnInit { this.users.forEach(u => u.selected = !u.selected); } - public selectSingleUser(user: IMassEmailUserModel) { - user.selected = !user.selected; - } - public send() { if(!this.subject) { this.missingSubject = true; diff --git a/src/Ombi/ClientApp/app/vote/vote.component.html b/src/Ombi/ClientApp/app/vote/vote.component.html index 6d8f8a797..d91ce85bd 100644 --- a/src/Ombi/ClientApp/app/vote/vote.component.html +++ b/src/Ombi/ClientApp/app/vote/vote.component.html @@ -32,16 +32,16 @@ - - poster - {{vm.title}} + {{vm.title}} @@ -65,10 +65,10 @@ - - @@ -76,7 +76,7 @@ height: auto; width: 100%;" (click)="toggle($event, vm.image)" src="{{vm.image}}" alt="poster"> - {{vm.title}} + {{vm.title}} diff --git a/src/Ombi/Controllers/IdentityController.cs b/src/Ombi/Controllers/IdentityController.cs index d138e973e..61b3f06d8 100644 --- a/src/Ombi/Controllers/IdentityController.cs +++ b/src/Ombi/Controllers/IdentityController.cs @@ -240,6 +240,7 @@ namespace Ombi.Controllers await CreateRole(OmbiRoles.RequestTv); await CreateRole(OmbiRoles.Disabled); await CreateRole(OmbiRoles.ReceivesNewsletter); + await CreateRole(OmbiRoles.ManageOwnRequests); } private async Task CreateRole(string role) diff --git a/src/Ombi/Controllers/ImagesController.cs b/src/Ombi/Controllers/ImagesController.cs index 6c05cdd3a..acbdfb9bc 100644 --- a/src/Ombi/Controllers/ImagesController.cs +++ b/src/Ombi/Controllers/ImagesController.cs @@ -39,7 +39,7 @@ namespace Ombi.Controllers { return string.Empty; } - var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.Get(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1)); + var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.GetAsync(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1)); var images = await FanartTvApi.GetTvImages(tvdbid, key.Value); if (images == null) @@ -64,7 +64,7 @@ namespace Ombi.Controllers [HttpGet("poster/movie/{movieDbId}")] public async Task GetMoviePoster(string movieDbId) { - var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.Get(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1)); + var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.GetAsync(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1)); var images = await FanartTvApi.GetMovieImages(movieDbId, key.Value); @@ -98,7 +98,7 @@ namespace Ombi.Controllers { return string.Empty; } - var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.Get(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1)); + var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.GetAsync(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1)); var images = await FanartTvApi.GetTvImages(tvdbid, key.Value); @@ -128,7 +128,7 @@ namespace Ombi.Controllers [HttpGet("background/movie/{movieDbId}")] public async Task GetMovieBackground(string movieDbId) { - var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.Get(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1)); + var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.GetAsync(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1)); var images = await FanartTvApi.GetMovieImages(movieDbId, key.Value); @@ -157,7 +157,7 @@ namespace Ombi.Controllers { return string.Empty; } - var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.Get(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1)); + var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.GetAsync(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1)); var images = await FanartTvApi.GetTvImages(tvdbid, key.Value); @@ -189,7 +189,7 @@ namespace Ombi.Controllers var movieUrl = string.Empty; var tvUrl = string.Empty; - var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.Get(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1)); + var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.GetAsync(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1)); if (moviesArray.Length > 0) { diff --git a/src/Ombi/Controllers/MusicRequestController.cs b/src/Ombi/Controllers/MusicRequestController.cs index 0d763cd86..fee0cc39d 100644 --- a/src/Ombi/Controllers/MusicRequestController.cs +++ b/src/Ombi/Controllers/MusicRequestController.cs @@ -4,10 +4,13 @@ using Ombi.Core.Engine; using Ombi.Core.Models.Requests; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Ombi.Store.Entities.Requests; using Ombi.Attributes; using Ombi.Core.Models; using Ombi.Core.Models.UI; +using Ombi.Store.Entities; +using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Ombi.Controllers { @@ -16,12 +19,16 @@ namespace Ombi.Controllers [Produces("application/json")] public class MusicRequestController : Controller { - public MusicRequestController(IMusicRequestEngine engine) + public MusicRequestController(IMusicRequestEngine engine, IVoteEngine voteEngine, ILogger log) { _engine = engine; + _voteEngine = voteEngine; + _log = log; } private readonly IMusicRequestEngine _engine; + private readonly IVoteEngine _voteEngine; + private readonly ILogger _log; /// /// Gets album requests. @@ -66,9 +73,19 @@ namespace Ombi.Controllers /// The album. /// [HttpPost] - public async Task Request([FromBody] MusicAlbumRequestViewModel album) + public async Task RequestAlbum([FromBody] MusicAlbumRequestViewModel album) { - return await _engine.RequestAlbum(album); + var result = await _engine.RequestAlbum(album); + if (result.Result) + { + var voteResult = await _voteEngine.UpVote(result.RequestId, RequestType.Album); + if (voteResult.IsError) + { + _log.LogError("Couldn't automatically add the vote for the album {0} because {1}", album.ForeignAlbumId, voteResult.ErrorMessage); + } + } + + return result; } /// @@ -88,7 +105,7 @@ namespace Ombi.Controllers /// The request identifier. /// [HttpDelete("{requestId:int}")] - [PowerUser] + [Authorize(Roles = "Admin,PowerUser,ManageOwnRequests")] public async Task DeleteRequest(int requestId) { await _engine.RemoveAlbumRequest(requestId); diff --git a/src/Ombi/Controllers/RequestController.cs b/src/Ombi/Controllers/RequestController.cs index 25270f9dd..9e13e1c90 100644 --- a/src/Ombi/Controllers/RequestController.cs +++ b/src/Ombi/Controllers/RequestController.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Ombi.Store.Entities.Requests; using System.Diagnostics; +using Microsoft.Extensions.Logging; using Ombi.Attributes; using Ombi.Core.Models.UI; using Ombi.Models; @@ -21,14 +22,19 @@ namespace Ombi.Controllers [Produces("application/json")] public class RequestController : Controller { - public RequestController(IMovieRequestEngine engine, ITvRequestEngine tvRequestEngine) + public RequestController(IMovieRequestEngine engine, ITvRequestEngine tvRequestEngine, IVoteEngine vote, + ILogger log) { MovieRequestEngine = engine; TvRequestEngine = tvRequestEngine; + VoteEngine = vote; + Log = log; } private IMovieRequestEngine MovieRequestEngine { get; } private ITvRequestEngine TvRequestEngine { get; } + private IVoteEngine VoteEngine { get; } + private ILogger Log { get; } /// /// Gets movie requests. @@ -75,7 +81,17 @@ namespace Ombi.Controllers [HttpPost("movie")] public async Task RequestMovie([FromBody] MovieRequestViewModel movie) { - return await MovieRequestEngine.RequestMovie(movie); + var result = await MovieRequestEngine.RequestMovie(movie); + if (result.Result) + { + var voteResult = await VoteEngine.UpVote(result.RequestId, RequestType.Movie); + if (voteResult.IsError) + { + Log.LogError("Couldn't automatically add the vote for the movie {0} because {1}", movie.TheMovieDbId, voteResult.ErrorMessage); + } + } + + return result; } /// @@ -95,12 +111,24 @@ namespace Ombi.Controllers /// The request identifier. /// [HttpDelete("movie/{requestId:int}")] - [PowerUser] + [Authorize(Roles = "Admin,PowerUser,ManageOwnRequests")] public async Task DeleteRequest(int requestId) { await MovieRequestEngine.RemoveMovieRequest(requestId); } + /// + /// Deletes the specified movie request. + /// + /// The request identifier. + /// + [HttpDelete("movie/all")] + [PowerUser] + public async Task DeleteAllRequests() + { + await MovieRequestEngine.RemoveAllMovieRequests(); + } + /// /// Updates the specified movie request. /// @@ -249,7 +277,17 @@ namespace Ombi.Controllers [HttpPost("tv")] public async Task RequestTv([FromBody] TvRequestViewModel tv) { - return await TvRequestEngine.RequestTvShow(tv); + var result = await TvRequestEngine.RequestTvShow(tv); + if (result.Result) + { + var voteResult = await VoteEngine.UpVote(result.RequestId, RequestType.TvShow); + if (voteResult.IsError) + { + Log.LogError("Couldn't automatically add the vote for the tv {0} because {1}", tv.TvDbId, voteResult.ErrorMessage); + } + } + + return result; } /// @@ -269,7 +307,7 @@ namespace Ombi.Controllers /// The request identifier. /// [HttpDelete("tv/{requestId:int}")] - [PowerUser] + [Authorize(Roles = "Admin,PowerUser,ManageOwnRequests")] public async Task DeleteTvRequest(int requestId) { await TvRequestEngine.RemoveTvRequest(requestId); @@ -380,7 +418,7 @@ namespace Ombi.Controllers /// /// The model. /// - [PowerUser] + [Authorize(Roles = "Admin,PowerUser,ManageOwnRequests")] [HttpDelete("tv/child/{requestId:int}")] public async Task DeleteChildRequest(int requestId) { diff --git a/src/Ombi/Program.cs b/src/Ombi/Program.cs index 7e9fa6f78..3a11093d2 100644 --- a/src/Ombi/Program.cs +++ b/src/Ombi/Program.cs @@ -45,7 +45,9 @@ namespace Ombi var urlValue = string.Empty; var instance = StoragePathSingleton.Instance; instance.StoragePath = storagePath ?? string.Empty; - using (var ctx = new OmbiContext()) + // Check if we need to migrate the settings + CheckAndMigrate(); + using (var ctx = new SettingsContext()) { var config = ctx.ApplicationConfigurations.ToList(); var url = config.FirstOrDefault(x => x.Type == ConfigurationTypes.Url); @@ -82,7 +84,7 @@ namespace Ombi ctx.SaveChanges(); } } - else if(baseUrl.HasValue() && !baseUrl.Equals(dbBaseUrl.Value)) + else if (baseUrl.HasValue() && !baseUrl.Equals(dbBaseUrl.Value)) { dbBaseUrl.Value = baseUrl; ctx.SaveChanges(); @@ -96,6 +98,139 @@ namespace Ombi BuildWebHost(args).Run(); } + /// + /// This is to remove the Settings from the Ombi.db to the "new" + /// OmbiSettings.db + /// + /// Ombi is hitting a limitation with SQLite where there is a lot of database activity + /// and SQLite does not handle concurrency at all, causing db locks. + /// + /// Splitting it all out into it's own DB helps with this. + /// + private static void CheckAndMigrate() + { + var doneGlobal = false; + var doneConfig = false; + using (var ombi = new OmbiContext()) + using (var settings = new SettingsContext()) + { + try + { + if (ombi.Settings.Any()) + { + // OK migrate it! + var allSettings = ombi.Settings.ToList(); + settings.Settings.AddRange(allSettings); + doneGlobal = true; + } + + // Check for any application settings + + if (ombi.ApplicationConfigurations.Any()) + { + // OK migrate it! + var allSettings = ombi.ApplicationConfigurations.ToList(); + settings.ApplicationConfigurations.AddRange(allSettings); + doneConfig = true; + } + + settings.SaveChanges(); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + + // Now delete the old stuff + if (doneGlobal) + ombi.Database.ExecuteSqlCommand("DELETE FROM GlobalSettings"); + if (doneConfig) + ombi.Database.ExecuteSqlCommand("DELETE FROM ApplicationConfiguration"); + + } + + // Now migrate all the external stuff + using (var ombi = new OmbiContext()) + using (var external = new ExternalContext()) + { + try + { + + if (ombi.PlexEpisode.Any()) + { + external.PlexEpisode.AddRange(ombi.PlexEpisode.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM PlexEpisode"); + } + + if (ombi.PlexSeasonsContent.Any()) + { + external.PlexSeasonsContent.AddRange(ombi.PlexSeasonsContent.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM PlexSeasonsContent"); + } + if (ombi.PlexServerContent.Any()) + { + external.PlexServerContent.AddRange(ombi.PlexServerContent.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM PlexServerContent"); + } + if (ombi.EmbyEpisode.Any()) + { + external.EmbyEpisode.AddRange(ombi.EmbyEpisode.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM EmbyEpisode"); + } + + if (ombi.EmbyContent.Any()) + { + external.EmbyContent.AddRange(ombi.EmbyContent.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM EmbyContent"); + } + if (ombi.RadarrCache.Any()) + { + external.RadarrCache.AddRange(ombi.RadarrCache.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM RadarrCache"); + } + if (ombi.SonarrCache.Any()) + { + external.SonarrCache.AddRange(ombi.SonarrCache.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM SonarrCache"); + } + if (ombi.LidarrAlbumCache.Any()) + { + external.LidarrAlbumCache.AddRange(ombi.LidarrAlbumCache.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM LidarrAlbumCache"); + } + if (ombi.LidarrArtistCache.Any()) + { + external.LidarrArtistCache.AddRange(ombi.LidarrArtistCache.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM LidarrArtistCache"); + } + if (ombi.SickRageEpisodeCache.Any()) + { + external.SickRageEpisodeCache.AddRange(ombi.SickRageEpisodeCache.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM SickRageEpisodeCache"); + } + if (ombi.SickRageCache.Any()) + { + external.SickRageCache.AddRange(ombi.SickRageCache.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM SickRageCache"); + } + if (ombi.CouchPotatoCache.Any()) + { + external.CouchPotatoCache.AddRange(ombi.CouchPotatoCache.ToList()); + ombi.Database.ExecuteSqlCommand("DELETE FROM CouchPotatoCache"); + } + + external.SaveChanges(); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + + } + private static void DeleteSchedulesDb() { try diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index 2d86d4471..cad5da907 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -177,7 +177,7 @@ namespace Ombi // Check if it's in the startup args var appConfig = serviceProvider.GetService(); - var baseUrl = appConfig.Get(ConfigurationTypes.BaseUrl).Result; + var baseUrl = appConfig.Get(ConfigurationTypes.BaseUrl); if (baseUrl != null) { if (baseUrl.Value.HasValue()) diff --git a/src/Ombi/cypress/fixtures/login.json b/src/Ombi/cypress/fixtures/login.json new file mode 100644 index 000000000..0b653fdfc --- /dev/null +++ b/src/Ombi/cypress/fixtures/login.json @@ -0,0 +1,4 @@ +{ + "username": "automation", + "password": "password" +} \ No newline at end of file diff --git a/src/Ombi/cypress/integration/vote.feature.spec.js b/src/Ombi/cypress/integration/vote.feature.spec.js new file mode 100644 index 000000000..8953a430c --- /dev/null +++ b/src/Ombi/cypress/integration/vote.feature.spec.js @@ -0,0 +1,140 @@ +/// + +describe('Voting Feature', function () { + beforeEach(function () { + cy.login('automation', 'password').then(() => { + + cy.removeAllMovieRequests(); + + cy.createUser('basicUser', 'password', [{ + value: "requestmovie", + Enabled: "true", + }, { + value: "requesttv", + Enabled: "true", + }, { + value: "requestmusic", + Enabled: "true", + }, + ]); + + cy.createUser('basicUser2', 'password', [{ + value: "requestmovie", + Enabled: "true", + }, { + value: "requesttv", + Enabled: "true", + }, { + value: "requestmusic", + Enabled: "true", + }, + ]); + + // Enable voting + cy.request({ + method: 'POST', + url: '/api/v1/Settings/vote', + body: { + Enabled: true, + MovieVoteMax: 2, + MusicVoteMax: 2, + TvShowVoteMax: 2, + }, + headers: { + 'Authorization': 'Bearer ' + window.localStorage.getItem('id_token'), + } + }); + + // Login as regular user + cy.login('basicUser', 'password').then(() => { + + cy.visit('/vote'); + }); + + }); + }); + + /// + /// Make sure we can load the page + /// + it('Loads votes page', function () { + // cy.login('basicUser','password'); + cy.contains("Vote"); + }); + + /// + /// Make sure that when we request a movie it automatically get's upvoted + /// + it('Request Movie automatically upvotes when I am the requestor', function () { + cy.requestMovie(335983).then(() => { + cy.visit('/vote'); + cy.get('#completedVotes').click(); + cy.contains('Venom').should('have.attr', 'data-test').then(($id) => { + cy.get('#' + $id + 'upvote').should('have.attr', 'disabled'); + cy.get('#' + $id + 'downvote').should('not.have.attr', 'disabled'); + }); + }); + }); + + /// + /// Make sure that when we request a tv show it automatically get's upvoted + /// + it('Request TV automatically upvotes when I am the requestor', function () { + cy.requestAllTv(305288).then(() => { + cy.visit('/vote'); + cy.get('#completedVotes').click(); + cy.contains('Stranger Things').should('have.attr', 'data-test').then(($id) => { + cy.get('#' + $id + 'upvote').should('have.attr', 'disabled'); + cy.get('#' + $id + 'downvote').should('not.have.attr', 'disabled'); + }); + }); + }); + + /// + /// Upvotes a movie with a different user, the votes should eq 2 + /// Meaning it should be approved now + /// + it.only('Upvote Movie to be approved', function () { + cy.login('basicUser2', 'password').then(() => { + cy.requestMovie(439079).then(() => { + cy.login('basicUser', 'password').then(() => { + + cy.visit('/vote'); + cy.contains('The Nun').should('have.attr', 'data-test').then(($id) => { + cy.get('#' + $id + 'upvote').click(); + cy.verifyNotification('Voted!'); + + // Verify it's in the completed panel + cy.get('#completedVotes').click(); cy.contains('The Nun').should('have.attr', 'data-test').then(($id) => { + cy.get('#' + $id + 'upvote').should('have.attr', 'disabled'); + cy.get('#' + $id + 'downvote').should('not.have.attr', 'disabled'); + }); + }); + }); + }); + }); + }); + + it.only('Downvote Movie', function () { + cy.login('basicUser2', 'password').then(() => { + cy.requestMovie(439079).then(() => { + cy.login('basicUser', 'password').then(() => { + + cy.visit('/vote'); + cy.contains('The Nun').should('have.attr', 'data-test').then(($id) => { + cy.get('#' + $id + 'downvote').click(); + cy.verifyNotification('Voted!'); + + // Verify it's in the completed panel + cy.get('#completedVotes').click(); cy.contains('The Nun').should('have.attr', 'data-test').then(($id) => { + cy.get('#' + $id + 'upvote').should('not.have.attr', 'disabled'); + cy.get('#' + $id + 'downvote').should('have.attr', 'disabled'); + }); + }); + }); + }); + }); + }); + + +}); \ No newline at end of file diff --git a/src/Ombi/cypress/integration/wizard.spec.js b/src/Ombi/cypress/integration/wizard.spec.js index d1487c8ca..253922496 100644 --- a/src/Ombi/cypress/integration/wizard.spec.js +++ b/src/Ombi/cypress/integration/wizard.spec.js @@ -1,7 +1,9 @@ +/// + describe('Wizard Setup Tests', function() { it('Setup Wizard User', function() { - cy.visit('http://localhost:3577/'); - cy.url().should('include', '/Wizard') + cy.visit('/'); + cy.url().should('include', 'Wizard') cy.get('[data-test=nextbtn]').click(); diff --git a/src/Ombi/cypress/support/commands.js b/src/Ombi/cypress/support/commands.js index b44a1e2b5..f4e4bfb4e 100644 --- a/src/Ombi/cypress/support/commands.js +++ b/src/Ombi/cypress/support/commands.js @@ -25,6 +25,7 @@ // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) Cypress.Commands.add('login', (username, password) => { + cy.clearLocalStorage(); cy.request({ method: 'POST', url: '/api/v1/token', @@ -34,9 +35,12 @@ Cypress.Commands.add('login', (username, password) => { } }) .then((resp) => { - window.localStorage.setItem('id_token', resp.body.access_token) + window.localStorage.setItem('id_token', resp.body.access_token); }); }); +Cypress.Commands.add('removeLogin', () => { + window.localStorage.removeItem('id_token'); +}); Cypress.Commands.add('createUser', (username, password, claims) => { cy.request({ diff --git a/src/Ombi/cypress/support/index.js b/src/Ombi/cypress/support/index.js index d68db96df..38ca34f83 100644 --- a/src/Ombi/cypress/support/index.js +++ b/src/Ombi/cypress/support/index.js @@ -14,7 +14,8 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import './commands'; +import './request.commands'; // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/src/Ombi/cypress/support/request.commands.js b/src/Ombi/cypress/support/request.commands.js new file mode 100644 index 000000000..4c936a4bc --- /dev/null +++ b/src/Ombi/cypress/support/request.commands.js @@ -0,0 +1,50 @@ + +Cypress.Commands.add('requestGenericMovie', () => { + cy.request({ + method: 'POST', + url: '/api/v1/request/movie', + body: { + TheMovieDbId: 299536 + }, + headers: { + 'Authorization': 'Bearer ' + window.localStorage.getItem('id_token'), + } + }) +}) + +Cypress.Commands.add('requestMovie', (movieId) => { + cy.request({ + method: 'POST', + url: '/api/v1/request/movie', + body: { + TheMovieDbId: movieId + }, + headers: { + 'Authorization': 'Bearer ' + window.localStorage.getItem('id_token'), + } + }) +}) + +Cypress.Commands.add('requestAllTv', (tvId) => { + cy.request({ + method: 'POST', + url: '/api/v1/request/tv', + body: { + TvDbId: tvId, + RequestAll: true + }, + headers: { + 'Authorization': 'Bearer ' + window.localStorage.getItem('id_token'), + } + }); +}) + +Cypress.Commands.add('removeAllMovieRequests', () => { + cy.request({ + method: 'DELETE', + url: '/api/v1/request/movie/all', + headers: { + 'Authorization': 'Bearer ' + window.localStorage.getItem('id_token'), + } + }); +}) \ No newline at end of file diff --git a/src/Ombi/package.json b/src/Ombi/package.json index 4c5302d6c..fa476f66b 100644 --- a/src/Ombi/package.json +++ b/src/Ombi/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "vendor": "gulp vendor", + "main": "gulp main", "lint": "tslint -p .", "publish": "gulp publish", "restore": "dotnet restore && yarn install", @@ -90,7 +91,8 @@ "zone.js": "^0.8.26" }, "resolutions": { - "@types/tapable": "1.0.0" + "@types/tapable": "1.0.0", + "cypress/**/@types/sinon": "4.3.3" }, "devDependencies": { "cypress": "^3.1.0" diff --git a/src/Ombi/tsconfig.json b/src/Ombi/tsconfig.json index bd114dcdb..1e41f53b9 100644 --- a/src/Ombi/tsconfig.json +++ b/src/Ombi/tsconfig.json @@ -28,9 +28,6 @@ } ] }, - "types": [ - "cypress" - ], "include": [ "ClientApp/**/*", "typings/**/*", diff --git a/src/Ombi/wwwroot/images/default-music-placeholder.png b/src/Ombi/wwwroot/images/default-music-placeholder.png index f6decc9e0..4cff17018 100644 Binary files a/src/Ombi/wwwroot/images/default-music-placeholder.png and b/src/Ombi/wwwroot/images/default-music-placeholder.png differ diff --git a/src/Ombi/wwwroot/images/default_movie_poster.png b/src/Ombi/wwwroot/images/default_movie_poster.png index f5cec85a5..fe990f92a 100644 Binary files a/src/Ombi/wwwroot/images/default_movie_poster.png and b/src/Ombi/wwwroot/images/default_movie_poster.png differ diff --git a/src/Ombi/wwwroot/images/default_tv_poster.png b/src/Ombi/wwwroot/images/default_tv_poster.png index 28e845ea5..ea18f98e8 100644 Binary files a/src/Ombi/wwwroot/images/default_tv_poster.png and b/src/Ombi/wwwroot/images/default_tv_poster.png differ diff --git a/src/Ombi/wwwroot/images/favicon/apple-touch-icon.png b/src/Ombi/wwwroot/images/favicon/apple-touch-icon.png index 1e6943041..069c32adb 100644 Binary files a/src/Ombi/wwwroot/images/favicon/apple-touch-icon.png and b/src/Ombi/wwwroot/images/favicon/apple-touch-icon.png differ diff --git a/src/Ombi/wwwroot/translations/fr.json b/src/Ombi/wwwroot/translations/fr.json index bf6e26a93..013dc9865 100644 --- a/src/Ombi/wwwroot/translations/fr.json +++ b/src/Ombi/wwwroot/translations/fr.json @@ -1,6 +1,6 @@ { "Login": { - "SignInButton": "Connexion", + "SignInButton": "Se connecter", "UsernamePlaceholder": "Nom d’utilisateur", "PasswordPlaceholder": "Mot de passe", "RememberMe": "Se souvenir de moi", @@ -12,8 +12,8 @@ "Common": { "ContinueButton": "Continuer", "Available": "Disponible", - "PartiallyAvailable": "Partially Available", - "Monitored": "Monitored", + "PartiallyAvailable": "Partiellement disponible", + "Monitored": "Suivi", "NotAvailable": "Non disponible", "ProcessingRequest": "En cours de traitement", "PendingApproval": "En attente d'approbation", @@ -37,10 +37,10 @@ "OnlineParagraph": "Le serveur média est actuellement en ligne", "PartiallyOnlineHeading": "Partiellement en ligne", "PartiallyOnlineParagraph": "Le serveur média est partiellement en ligne.", - "MultipleServersUnavailable": "Il y a {{serversUnavailable}} serveurs hors ligne sur {{totalServers}}.", - "SingleServerUnavailable": "Il y a {{serversUnavailable}} serveur hors ligne sur {{totalServers}}.", - "OfflineHeading": "Actuellement hors ligne", - "OfflineParagraph": "Le serveur média est actuellement hors ligne.", + "MultipleServersUnavailable": "Il y a {{serversUnavailable}} serveurs hors-ligne sur {{totalServers}}.", + "SingleServerUnavailable": "Il y a {{serversUnavailable}} serveur hors-ligne sur {{totalServers}}.", + "OfflineHeading": "Actuellement hors-ligne", + "OfflineParagraph": "Le serveur média est actuellement hors-ligne.", "CheckPageForUpdates": "Consultez cette page pour voir les mises à jour du site." }, "NavigationBar": { @@ -65,7 +65,7 @@ "Paragraph": "Vous voulez regarder quelque chose qui n'est pas disponible actuellement ? Pas de problème, recherchez-le ci-dessous et demandez-le !", "MoviesTab": "Films", "TvTab": "TV", - "MusicTab": "Music", + "MusicTab": "Musique", "Suggestions": "Suggestions", "NoResults": "Désolé, nous n'avons trouvé aucun résultat !", "DigitalDate": "Sortie numérique: {{date}}", @@ -103,13 +103,13 @@ "Paragraph": "Vous pouvez voir ci-dessous vos demandes et celles des autres, ainsi que leur statut de téléchargement et d'approbation.", "MoviesTab": "Films", "TvTab": "Émissions", - "MusicTab": "Music", + "MusicTab": "Musique", "RequestedBy": "Demandé par :", "Status": "Statut :", "RequestStatus": "Statut de la demande :", "Denied": " Refusé :", "TheatricalRelease": "Sortie en salle: {{date}}", - "ReleaseDate": "Released: {{date}}", + "ReleaseDate": "Sortie : {{date}}", "TheatricalReleaseSort": "Sortie en salle", "DigitalRelease": "Sortie numérique: {{date}}", "RequestDate": "Date de la demande :", @@ -136,11 +136,11 @@ "SortStatusAsc": "Statut ▲", "SortStatusDesc": "Statut ▼", "Remaining": { - "Quota": "{{remaining}}/{{total}} requests remaining", - "NextDays": "Another request will be added in {{time}} days", - "NextHours": "Another request will be added in {{time}} hours", - "NextMinutes": "Another request will be added in {{time}} minutes", - "NextMinute": "Another request will be added in {{time}} minute" + "Quota": "{{remaining}}/{{total}} demande(s) restante(s)", + "NextDays": "Une autre demande sera ajoutée dans {{time}} jours", + "NextHours": "Une autre demande sera ajoutée dans {{time}} heures", + "NextMinutes": "Une autre demande sera ajoutée dans {{time}} minutes", + "NextMinute": "Une autre demande sera ajoutée dans {{time}} minute" } }, "Issues": { @@ -170,15 +170,15 @@ "PendingApproval": "En attente de validation" }, "UserManagment": { - "TvRemaining": "TV: {{remaining}}/{{total}} remaining", - "MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", - "MusicRemaining": "Music: {{remaining}}/{{total}} remaining", - "TvDue": "TV: {{date}}", - "MovieDue": "Movie: {{date}}", - "MusicDue": "Music: {{date}}" + "TvRemaining": "TV : {{remaining}}/{{total}} restant(s)", + "MovieRemaining": "Films : {{remaining}}/{{total}} restant(s)", + "MusicRemaining": "Musique : {{remaining}}/{{total}} restant(s)", + "TvDue": "TV : {{date}}", + "MovieDue": "Film : {{date}}", + "MusicDue": "Musique : {{date}}" }, "Votes": { - "CompletedVotesTab": "Voted", - "VotesTab": "Votes Needed" + "CompletedVotesTab": "Voté", + "VotesTab": "Votes nécessaires" } } \ No newline at end of file diff --git a/src/Ombi/yarn.lock b/src/Ombi/yarn.lock index 1debb9f2d..77c0b08f6 100644 --- a/src/Ombi/yarn.lock +++ b/src/Ombi/yarn.lock @@ -210,13 +210,9 @@ "@types/chai" "*" "@types/sinon" "*" -"@types/sinon@*": - version "5.0.4" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-5.0.4.tgz#a765b390b373cf01a3b19b0c97f9eb4bb2a168b1" - -"@types/sinon@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-4.0.0.tgz#9a93ffa4ee1329e85166278a5ed99f81dc4c8362" +"@types/sinon@*", "@types/sinon@4.0.0", "@types/sinon@4.3.3": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-4.3.3.tgz#97cbbfddc3282b5fd40c7abf80b99db426fd4237" "@types/sizzle@*": version "2.3.2"